/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006 Robert Beckebans <trebor_7@users.sourceforge.net>

This file is part of XreaL source code.

XreaL source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

XreaL source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with XreaL source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <errno.h>

#ifdef __linux__				// rb010123
#include <mntent.h>
#endif

#include <dlfcn.h>

#ifdef __linux__
#include <fpu_control.h>		// bk001213 - force dumps on divide by zero
#endif

// FIXME TTimo should we gard this? most *nix system should comply?
#include <termios.h>

#include "../common/shared.h"
#include "../common/common.h"
#include "../renderer/r_public.h"

#include "linux_local.h"		// bk001204

// Structure containing functions exported from refresh DLL
refexport_t     re;

unsigned        sys_frame_time;

uid_t           saved_euid;
qboolean        stdin_active = qtrue;

// =============================================================
// tty console variables
// =============================================================

// enable/disabled tty input mode
// NOTE TTimo this is used during startup, cannot be changed during run
static cvar_t  *ttycon = NULL;

// general flag to tell about tty console mode
static qboolean ttycon_on = qfalse;

// when printing general stuff to stdout stderr (Sys_Printf)
//   we need to disable the tty console stuff
// this increments so we can recursively disable
static int      ttycon_hide = 0;

// some key codes that the terminal may be using
// TTimo NOTE: I'm not sure how relevant this is
static int      tty_erase;
static int      tty_eof;

static struct termios tty_tc;

static field_t  tty_con;

// history
// NOTE TTimo this is a bit duplicate of the graphical console history
//   but it's safer and faster to write our own here
#define TTY_HISTORY 32
static field_t  ttyEditLines[TTY_HISTORY];
static int      hist_current = -1, hist_count = 0;

// =======================================================================
// General routines
// =======================================================================

// bk001207 
#define MEM_THRESHOLD 96*1024*1024

/*
==================
Sys_LowPhysicalMemory
==================
*/
qboolean Sys_LowPhysicalMemory()
{
	return qfalse;				// bk001207 - FIXME
}

/*
==================
Sys_FunctionCmp
==================
*/
int Sys_FunctionCmp(void *f1, void *f2)
{
	return qtrue;
}

/*
==================
Sys_FunctionCheckSum
==================
*/
int Sys_FunctionCheckSum(void *f1)
{
	return 0;
}

/*
=================
Sys_In_Restart_f

Restart the input subsystem
=================
*/
void Sys_In_Restart_f(void)
{
	IN_Shutdown();
	IN_Init();
}

// =============================================================
// 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 tty_Clear and tty_Show to be called before and after a stdout or stderr output
// =============================================================

// flush stdin, I suspect some terminals are sending a LOT of shit
// FIXME TTimo relevant?
void tty_FlushIn()
{
	char            key;

	while(read(0, &key, 1) != -1);
}

// do a backspace
// TTimo NOTE: it seems on some terminals just sending '\b' is not enough
//   so for now, in any case we send "\b \b" .. yeah well ..
//   (there may be a way to find out if '\b' alone would work though)
void tty_Back()
{
	char            key;

	key = '\b';
	write(1, &key, 1);
	key = ' ';
	write(1, &key, 1);
	key = '\b';
	write(1, &key, 1);
}

// clear the display of the line currently edited
// bring cursor back to beginning of line
void tty_Hide()
{
	int             i;

	assert(ttycon_on);
	if(ttycon_hide)
	{
		ttycon_hide++;
		return;
	}
	if(tty_con.cursor > 0)
	{
		for(i = 0; i < tty_con.cursor; i++)
		{
			tty_Back();
		}
	}
	ttycon_hide++;
}

// show the current line
// FIXME TTimo need to position the cursor if needed??
void tty_Show()
{
	int             i;

	assert(ttycon_on);
	assert(ttycon_hide > 0);
	ttycon_hide--;
	if(ttycon_hide == 0)
	{
		if(tty_con.cursor)
		{
			for(i = 0; i < tty_con.cursor; i++)
			{
				write(1, tty_con.buffer + i, 1);
			}
		}
	}
}

// never exit without calling this, or your terminal will be left in a pretty bad state
void Sys_ConsoleInputShutdown()
{
	if(ttycon_on)
	{
		Com_Printf("Shutdown tty console\n");
		tcsetattr(0, TCSADRAIN, &tty_tc);
	}
}

void Hist_Add(field_t * field)
{
	int             i;

	assert(hist_count <= TTY_HISTORY);
	assert(hist_count >= 0);
	assert(hist_current >= -1);
	assert(hist_current <= hist_count);
	// make some room
	for(i = TTY_HISTORY - 1; i > 0; i--)
	{
		ttyEditLines[i] = ttyEditLines[i - 1];
	}
	ttyEditLines[0] = *field;
	if(hist_count < TTY_HISTORY)
	{
		hist_count++;
	}
	hist_current = -1;			// re-init
}

field_t        *Hist_Prev()
{
	int             hist_prev;

	assert(hist_count <= TTY_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]);
}

field_t        *Hist_Next()
{
	assert(hist_count <= TTY_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]);
}

// =============================================================
// general sys routines
// =============================================================

// single exit point (regular exit or in case of signal fault)
void Sys_Exit(int ex)
{
	Sys_ConsoleInputShutdown();

#ifdef NDEBUG					// regular behavior

	// We can't do this 
	//  as long as GL DLL's keep installing with atexit...
	//exit(ex);
	_exit(ex);
#else

	// Give me a backtrace on error exits.
	assert(ex == 0);
	exit(ex);
#endif
}

void Sys_Quit(void)
{
	CL_Shutdown();
	fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) & ~FNDELAY);
	Sys_Exit(0);
}

void Sys_Init(void)
{
	Cmd_AddCommand("in_restart", Sys_In_Restart_f);

#if defined __linux__
#if defined __i386__
	Cvar_Set("arch", "linux i386");
#elif defined __x86_64__
	Cvar_Set("arch", "linux x86_64");
#elif defined __alpha__
	Cvar_Set("arch", "linux alpha");
#elif defined __sparc__
	Cvar_Set("arch", "linux sparc");
#elif defined __FreeBSD__

#if defined __i386__			// FreeBSD
	Cvar_Set("arch", "freebsd i386");
#elif defined __alpha__
	Cvar_Set("arch", "freebsd alpha");
#else
	Cvar_Set("arch", "freebsd unknown");
#endif							// FreeBSD

#else
	Cvar_Set("arch", "linux unknown");
#endif
#elif defined __sun__
#if defined __i386__
	Cvar_Set("arch", "solaris x86");
#elif defined __sparc__
	Cvar_Set("arch", "solaris sparc");
#else
	Cvar_Set("arch", "solaris unknown");
#endif
#elif defined __sgi__
#if defined __mips__
	Cvar_Set("arch", "sgi mips");
#else
	Cvar_Set("arch", "sgi unknown");
#endif
#else
	Cvar_Set("arch", "unknown");
#endif

	Cvar_Set("username", Sys_GetCurrentUser());

	IN_Init();
}

void Sys_Error(const char *error, ...)
{
	va_list         argptr;
	char            string[1024];

	// change stdin to non blocking
	// NOTE TTimo not sure how well that goes with tty console mode
	fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) & ~FNDELAY);

	// don't bother do a show on this one heh
	if(ttycon_on)
	{
		tty_Hide();
	}

	CL_Shutdown();

	va_start(argptr, error);
	vsprintf(string, error, argptr);
	va_end(argptr);
	fprintf(stderr, "Sys_Error: %s\n", string);

	Sys_Exit(1);				// bk010104 - use single exit point.
}

void Sys_Warn(char *warning, ...)
{
	va_list         argptr;
	char            string[1024];

	va_start(argptr, warning);
	vsprintf(string, warning, argptr);
	va_end(argptr);

	if(ttycon_on)
	{
		tty_Hide();
	}

	fprintf(stderr, "Warning: %s", string);

	if(ttycon_on)
	{
		tty_Show();
	}
}

/*
============
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;
}

void floating_point_exception_handler(int whatever)
{
	signal(SIGFPE, floating_point_exception_handler);
}

// initialize the console input (tty mode if wanted and possible)
void Sys_ConsoleInputInit()
{
	struct termios  tc;

	// TTimo 
	// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=390
	// ttycon 0 or 1, if the process is backgrounded (running non interactively)
	// then SIGTTIN or SIGTOU is emitted, if not catched, turns into a SIGSTP
	signal(SIGTTIN, SIG_IGN);
	signal(SIGTTOU, SIG_IGN);

	// FIXME TTimo initialize this in Sys_Init or something?
	ttycon = Cvar_Get("ttycon", "1", 0);
	if(ttycon && ttycon->value)
	{
		if(isatty(STDIN_FILENO) != 1)
		{
			Com_Printf("stdin is not a tty, tty console mode failed\n");
			Cvar_Set("ttycon", "0");
			ttycon_on = qfalse;
			return;
		}
		Com_Printf("Started tty console (use +set ttycon 0 to disable)\n");
		Field_Clear(&tty_con);
		tcgetattr(0, &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 sig­
		   nal
		 */
		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(0, TCSADRAIN, &tc);
		ttycon_on = qtrue;
	}
	else
		ttycon_on = qfalse;
}

char           *Sys_ConsoleInput(void)
{
	// we use this when sending back commands
	static char     text[256];
	int             avail;
	char            key;
	field_t        *history;

	if(ttycon && ttycon->value)
	{
		avail = read(0, &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';
					tty_Back();
				}
				return NULL;
			}
			// check if this is a control char
			if((key) && (key) < ' ')
			{
				if(key == '\n')
				{
					// push it in history
					Hist_Add(&tty_con);
					strcpy(text, tty_con.buffer);
					Field_Clear(&tty_con);
					key = '\n';
					write(1, &key, 1);
					return text;
				}
				if(key == '\t')
				{
					tty_Hide();
					Field_AutoComplete(&tty_con);
					tty_Show();
					return NULL;
				}
				avail = read(0, &key, 1);
				if(avail != -1)
				{
					// VT 100 keys
					if(key == '[' || key == 'O')
					{
						avail = read(0, &key, 1);
						if(avail != -1)
						{
							switch (key)
							{
								case 'A':
									history = Hist_Prev();
									if(history)
									{
										tty_Hide();
										tty_con = *history;
										tty_Show();
									}
									tty_FlushIn();
									return NULL;
									break;
								case 'B':
									history = Hist_Next();
									tty_Hide();
									if(history)
									{
										tty_con = *history;
									}
									else
									{
										Field_Clear(&tty_con);
									}
									tty_Show();
									tty_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);
				tty_FlushIn();
				return NULL;
			}
			// push regular character
			tty_con.buffer[tty_con.cursor] = key;
			tty_con.cursor++;
			// print the current line (this is differential)
			write(1, &key, 1);
		}
		return NULL;
	}
	else
	{
		int             len;
		fd_set          fdset;
		struct timeval  timeout;

		if(!com_dedicated || !com_dedicated->value)
			return NULL;

		if(!stdin_active)
			return NULL;

		FD_ZERO(&fdset);
		FD_SET(0, &fdset);		// stdin
		timeout.tv_sec = 0;
		timeout.tv_usec = 0;
		if(select(1, &fdset, NULL, NULL, &timeout) == -1 || !FD_ISSET(0, &fdset))
		{
			return NULL;
		}

		len = read(0, text, sizeof(text));
		if(len == 0)
		{						// eof!
			stdin_active = qfalse;
			return NULL;
		}

		if(len < 1)
			return NULL;
		text[len - 1] = 0;		// rip off the /n and terminate

		return text;
	}
}

/*****************************************************************************/

/*
=================
Sys_UnloadDll

=================
*/
void Sys_UnloadDll(void *dllHandle)
{
	// bk001206 - verbose error reporting
	const char     *err;		// rb010123 - now const

	if(!dllHandle)
	{
		Com_Printf("Sys_UnloadDll(NULL)\n");
		return;
	}

	dlclose(dllHandle);
	err = dlerror();

	if(err != NULL)
		Com_Printf("Sys_UnloadGame failed on dlclose: \"%s\"!\n", err);
}


/*
=================
Sys_LoadDll

Used to load a development dll instead of a virtual machine
TTimo:
changed the load procedure to match VFS logic, and allow developer use
#1 look down current path
#2 look in fs_homepath
#3 look in fs_basepath
=================
*/
extern char    *FS_BuildOSPath(const char *base, const char *game, const char *qpath);

void           *Sys_LoadDll(const char *name, char *fqpath, intptr_t (**entryPoint) (int, ...), intptr_t (*systemcalls) (intptr_t, ...))
{
	void           *libHandle;
	void            (*dllEntry) (intptr_t (*syscallptr) (intptr_t, ...));
	char            curpath[MAX_OSPATH];
	char            fname[MAX_OSPATH];
	char           *basepath;
	char           *homepath;
	char           *pwdpath;
	char           *gamedir;
	char           *fn;
	const char     *err = NULL;

	*fqpath = 0;

	// bk001206 - let's have some paranoia
	assert(name);

	getcwd(curpath, sizeof(curpath));
#if defined __i386__
	snprintf(fname, sizeof(fname), "%si386.so", name);
#elif defined __x86_64__
	snprintf(fname, sizeof(fname), "%sx86_64.so", name);
#elif defined __powerpc__		//rcg010207 - PPC support.
	snprintf(fname, sizeof(fname), "%sppc.so", name);
#elif defined __axp__
	snprintf(fname, sizeof(fname), "%saxp.so", name);
#elif defined __mips__
	snprintf(fname, sizeof(fname), "%smips.so", name);
#else
#error Unknown arch
#endif

// bk001129 - was RTLD_LAZY 
#define Q_RTLD    RTLD_NOW

	pwdpath = Sys_Cwd();
	basepath = Cvar_VariableString("fs_basepath");
	homepath = Cvar_VariableString("fs_homepath");
	gamedir = Cvar_VariableString("fs_game");

	// pwdpath
	fn = FS_BuildOSPath(pwdpath, gamedir, fname);
	Com_Printf("Sys_LoadDll(%s)... \n", fn);
	libHandle = dlopen(fn, Q_RTLD);

	if(!libHandle)
	{
		Com_Printf("Sys_LoadDll(%s) failed:\n\"%s\"\n", fn, dlerror());
		// fs_homepath
		fn = FS_BuildOSPath(homepath, gamedir, fname);
		Com_Printf("Sys_LoadDll(%s)... \n", fn);
		libHandle = dlopen(fn, Q_RTLD);

		if(!libHandle)
		{
			Com_Printf("Sys_LoadDll(%s) failed:\n\"%s\"\n", fn, dlerror());
			// fs_basepath
			fn = FS_BuildOSPath(basepath, gamedir, fname);
			Com_Printf("Sys_LoadDll(%s)... \n", fn);
			libHandle = dlopen(fn, Q_RTLD);

			if(!libHandle)
			{
#ifndef NDEBUG					// bk001206 - in debug abort on failure
				Com_Error(ERR_FATAL, "Sys_LoadDll(%s) failed dlopen() completely!\n", name);
#else
				Com_Printf("Sys_LoadDll(%s) failed dlopen() completely!\n", name);
#endif
				return NULL;
			}
			else
				Com_Printf("Sys_LoadDll(%s): succeeded ...\n", fn);
		}
		else
			Com_Printf("Sys_LoadDll(%s): succeeded ...\n", fn);
	}
	else
		Com_Printf("Sys_LoadDll(%s): succeeded ...\n", fn);

	dllEntry = dlsym(libHandle, "dllEntry");
	*entryPoint = dlsym(libHandle, "vmMain");
	if(!*entryPoint || !dllEntry)
	{
		err = dlerror();
#ifndef NDEBUG					// bk001206 - in debug abort on failure
		Com_Error(ERR_FATAL, "Sys_LoadDll(%s) failed dlsym(vmMain):\n\"%s\" !\n", name, err);
#else
		Com_Printf("Sys_LoadDll(%s) failed dlsym(vmMain):\n\"%s\" !\n", name, err);
#endif
		dlclose(libHandle);
		err = dlerror();
		if(err != NULL)
			Com_Printf("Sys_LoadDll(%s) failed dlcose:\n\"%s\"\n", name, err);
		return NULL;
	}
	Com_Printf("Sys_LoadDll(%s) found **vmMain** at  %p  \n", name, *entryPoint);	// bk001212
	dllEntry(systemcalls);
	Com_Printf("Sys_LoadDll(%s) succeeded!\n", name);
	if(libHandle)
		Q_strncpyz(fqpath, fn, MAX_QPATH);	// added 7/20/02 by T.Ray
	return libHandle;
}

/*
========================================================================

EVENT LOOP

========================================================================
*/

// bk000306: upped this from 64
#define	MAX_QUED_EVENTS		256
#define	MASK_QUED_EVENTS	( MAX_QUED_EVENTS - 1 )

sysEvent_t      eventQue[MAX_QUED_EVENTS];

// bk000306: initialize
int             eventHead = 0;
int             eventTail = 0;
byte            sys_packetReceived[MAX_MSGLEN];

/*
================
Sys_QueEvent

A time of 0 will get the current time
Ptr should either be null, or point to a block of data that can
be freed by the game later.
================
*/
void Sys_QueEvent(int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr)
{
	sysEvent_t     *ev;

	ev = &eventQue[eventHead & MASK_QUED_EVENTS];

	// bk000305 - was missing
	if(eventHead - eventTail >= MAX_QUED_EVENTS)
	{
		Com_Printf("Sys_QueEvent: overflow\n");
		// we are discarding an event, but don't leak memory
		if(ev->evPtr)
		{
			Z_Free(ev->evPtr);
		}
		eventTail++;
	}

	eventHead++;

	if(time == 0)
	{
		time = Sys_Milliseconds();
	}

	ev->evTime = time;
	ev->evType = type;
	ev->evValue = value;
	ev->evValue2 = value2;
	ev->evPtrLength = ptrLength;
	ev->evPtr = ptr;
}

/*
================
Sys_GetEvent

================
*/
sysEvent_t Sys_GetEvent(void)
{
	sysEvent_t      ev;
	char           *s;
	msg_t           netmsg;
	netadr_t        adr;

	// return if we have data
	if(eventHead > eventTail)
	{
		eventTail++;
		return eventQue[(eventTail - 1) & MASK_QUED_EVENTS];
	}

	// pump the message loop
	// in vga this calls KBD_Update, under X, it calls GetEvent
	Sys_SendKeyEvents();

	// check for console commands
	s = Sys_ConsoleInput();
	if(s)
	{
		char           *b;
		int             len;

		len = strlen(s) + 1;
		b = Z_Malloc(len);
		strcpy(b, s);
		Sys_QueEvent(0, SE_CONSOLE, 0, 0, len, b);
	}

	// check for other input devices
	IN_Frame();

	// check for network packets
	MSG_Init(&netmsg, sys_packetReceived, sizeof(sys_packetReceived));
	if(Sys_GetPacket(&adr, &netmsg))
	{
		netadr_t       *buf;
		int             len;

		// copy out to a seperate buffer for qeueing
		len = sizeof(netadr_t) + netmsg.cursize;
		buf = Z_Malloc(len);
		*buf = adr;
		memcpy(buf + 1, netmsg.data, netmsg.cursize);
		Sys_QueEvent(0, SE_PACKET, 0, 0, len, buf);
	}

	// return if we have data
	if(eventHead > eventTail)
	{
		eventTail++;
		return eventQue[(eventTail - 1) & MASK_QUED_EVENTS];
	}

	// create an empty event to return

	memset(&ev, 0, sizeof(ev));
	ev.evTime = Sys_Milliseconds();

	return ev;
}

/*****************************************************************************/

void Sys_AppActivate(void)
{
}

char *Sys_GetClipboardData(void)
{
	return NULL;
}

void Sys_Print(const char *msg)
{
	if(ttycon_on)
	{
		tty_Hide();
	}
	fputs(msg, stderr);
	if(ttycon_on)
	{
		tty_Show();
	}
}

void Sys_ConfigureFPU()
{								// bk001213 - divide by zero
#ifdef __linux__
#ifdef __i386
#ifndef NDEBUG

	// bk0101022 - enable FPE's in debug mode
	static int      fpu_word = _FPU_DEFAULT & ~(_FPU_MASK_ZM | _FPU_MASK_IM);
	int             current = 0;

	_FPU_GETCW(current);
	if(current != fpu_word)
	{
#if 0
		Com_Printf("FPU Control 0x%x (was 0x%x)\n", fpu_word, current);
		_FPU_SETCW(fpu_word);
		_FPU_GETCW(current);
		assert(fpu_word == current);
#endif
	}
#else							// NDEBUG
	static int      fpu_word = _FPU_DEFAULT;

	_FPU_SETCW(fpu_word);
#endif							// NDEBUG
#endif							// __i386
#endif							// __linux
}

void Sys_PrintBinVersion(const char *name)
{
	char           *date = __DATE__;
	char           *time = __TIME__;
	char           *sep = "==============================================================";

	fprintf(stdout, "\n\n%s\n", sep);
#ifdef DEDICATED
	fprintf(stdout, "*nix EvQ3 Dedicated Server [%s %s]\n", date, time);
#else
	fprintf(stdout, "*nix EvQ3 [%s %s]\n", date, time);
#endif
	fprintf(stdout, " local install: %s\n", name);
	fprintf(stdout, "%s\n\n", sep);
}

void Sys_ParseArgs(int argc, char *argv[])
{

	if(argc == 2)
	{
		if((!strcmp(argv[1], "--version")) || (!strcmp(argv[1], "-v")))
		{
			Sys_PrintBinVersion(argv[0]);
			Sys_Exit(0);
		}
	}
}

#include "../client/client.h"
extern clientStatic_t cls;

int main(int argc, char *argv[])
{
	// int    oldtime, newtime; // bk001204 - unused
	int             len, i;
	char           *cmdline;
	void            Sys_SetDefaultCDPath(const char *path);

	// go back to real user for config loads
	saved_euid = geteuid();
	seteuid(getuid());

	Sys_ParseArgs(argc, argv);	// bk010104 - added this for support

	Sys_SetDefaultCDPath(argv[0]);

	// merge the command line, this is kinda silly
	for(len = 1, i = 1; i < argc; i++)
		len += strlen(argv[i]) + 1;
	cmdline = malloc(len);
	*cmdline = 0;
	for(i = 1; i < argc; i++)
	{
		if(i > 1)
			strcat(cmdline, " ");
		strcat(cmdline, argv[i]);
	}

	// bk000306 - clear queues
	memset(&eventQue[0], 0, MAX_QUED_EVENTS * sizeof(sysEvent_t));
	memset(&sys_packetReceived[0], 0, MAX_MSGLEN * sizeof(byte));

	Com_Init(cmdline);
	NET_Init();

	Sys_ConsoleInputInit();

	fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) | FNDELAY);

#ifdef DEDICATED
	// init here for dedicated, as we don't have GLimp_Init
	InitSig();
#endif

	while(1)
	{
#ifdef __linux__
		Sys_ConfigureFPU();
#endif
		Com_Frame();
	}
}
