rallyunlimited-engine/code/unix/unix_main.c

970 lines
20 KiB
C
Raw Normal View History

2024-02-02 16:46:17 +00:00
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena 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.
Quake III Arena 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 Quake III Arena 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>
#include <libgen.h> // dirname
#include <dlfcn.h>
#ifdef __linux__
#ifdef __GLIBC__
#include <fpu_control.h> // bk001213 - force dumps on divide by zero
#endif
#endif
#if defined(__sun)
#include <sys/file.h>
#endif
// FIXME TTimo should we gard this? most *nix system should comply?
#include <termios.h>
#include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h"
#include "../renderercommon/tr_public.h"
#include "linux_local.h" // bk001204
#ifndef DEDICATED
#include "../client/client.h"
#endif
unsigned sys_frame_time;
qboolean stdin_active = qfalse;
int stdin_flags = 0;
// =============================================================
// tty console variables
// =============================================================
typedef enum {
TTY_ENABLED,
TTY_DISABLED,
TTY_ERROR
} tty_err;
// 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;
static cvar_t *ttycon_ansicolor = NULL;
static qboolean ttycon_color_on = qfalse;
tty_err Sys_ConsoleInputInit( void );
// =======================================================================
// General routines
// =======================================================================
// bk001207
#define MEM_THRESHOLD 96*1024*1024
/*
==================
Sys_LowPhysicalMemory()
==================
*/
qboolean Sys_LowPhysicalMemory( void )
{
//MEMORYSTATUS stat;
//GlobalMemoryStatus (&stat);
//return (stat.dwTotalPhys <= MEM_THRESHOLD) ? qtrue : qfalse;
return qfalse; // bk001207 - FIXME
}
void Sys_BeginProfiling( void )
{
}
// =============================================================
// 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?
static void tty_FlushIn( void )
{
#if 1
tcflush( STDIN_FILENO, TCIFLUSH );
#else
char key;
while ( read( STDIN_FILENO, &key, 1 ) > 0 );
#endif
}
// 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)
static void tty_Back( void )
{
write( STDOUT_FILENO, "\b \b", 3 );
}
// clear the display of the line currently edited
// bring cursor back to beginning of line
void tty_Hide( void )
{
int i;
if ( !ttycon_on )
return;
if ( ttycon_hide )
{
ttycon_hide++;
return;
}
if ( tty_con.cursor > 0 )
{
for ( i = 0; i < tty_con.cursor; i++ )
{
tty_Back();
}
}
tty_Back(); // delete "]" ? -EC-
ttycon_hide++;
}
// show the current line
// FIXME TTimo need to position the cursor if needed??
void tty_Show( void )
{
if ( !ttycon_on )
return;
if ( ttycon_hide > 0 )
{
ttycon_hide--;
if ( ttycon_hide == 0 )
{
write( STDOUT_FILENO, "]", 1 ); // -EC-
if ( tty_con.cursor > 0 )
{
write( STDOUT_FILENO, tty_con.buffer, tty_con.cursor );
}
}
}
}
// never exit without calling this, or your terminal will be left in a pretty bad state
void Sys_ConsoleInputShutdown( void )
{
if ( ttycon_on )
{
// Com_Printf( "Shutdown tty console\n" ); // -EC-
tty_Back(); // delete "]" ? -EC-
tcsetattr( STDIN_FILENO, TCSADRAIN, &tty_tc );
}
// Restore blocking to stdin reads
if ( stdin_active )
{
fcntl( STDIN_FILENO, F_SETFL, stdin_flags );
// fcntl( STDIN_FILENO, F_SETFL, fcntl( STDIN_FILENO, F_GETFL, 0 ) & ~O_NONBLOCK );
}
Com_Memset( &tty_con, 0, sizeof( tty_con ) );
stdin_active = qfalse;
ttycon_on = qfalse;
ttycon_hide = 0;
}
/*
==================
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 )
{
Sys_ConsoleInputInit();
}
void CON_SigTStp( int signum )
{
sigset_t mask;
tty_FlushIn();
Sys_ConsoleInputShutdown();
sigemptyset( &mask );
sigaddset( &mask, SIGTSTP );
sigprocmask( SIG_UNBLOCK, &mask, NULL );
signal( SIGTSTP, SIG_DFL );
kill( getpid(), SIGTSTP );
}
// =============================================================
// general sys routines
// =============================================================
// single exit point (regular exit or in case of signal fault)
void Sys_Exit( int code ) __attribute((noreturn));
void Sys_Exit( int code )
{
Sys_ConsoleInputShutdown();
#ifdef NDEBUG // regular behavior
// We can't do this
// as long as GL DLL's keep installing with atexit...
//exit(ex);
_exit( code );
#else
// Give me a backtrace on error exits.
assert( code == 0 );
exit( code );
#endif
}
void Sys_Quit( void )
{
#ifndef DEDICATED
CL_Shutdown( "", qtrue );
#endif
Sys_Exit( 0 );
}
void Sys_Init( void )
{
Cvar_Set( "arch", OS_STRING " " ARCH_STRING );
//IN_Init(); // rcg08312005 moved into glimp.
}
void Sys_Error( const char *format, ... )
{
va_list argptr;
char text[1024];
// change stdin to non blocking
// NOTE TTimo not sure how well that goes with tty console mode
if ( stdin_active )
{
// fcntl( STDIN_FILENO, F_SETFL, fcntl( STDIN_FILENO, F_GETFL, 0) & ~FNDELAY );
fcntl( STDIN_FILENO, F_SETFL, stdin_flags );
}
// don't bother do a show on this one heh
if ( ttycon_on )
{
tty_Hide();
}
va_start( argptr, format );
Q_vsnprintf( text, sizeof( text ), format, argptr );
va_end( argptr );
#ifndef DEDICATED
CL_Shutdown( text, qtrue );
#endif
fprintf( stderr, "Sys_Error: %s\n", text );
Sys_Exit( 1 ); // bk010104 - use single exit point.
}
void floating_point_exception_handler( int whatever )
{
signal( SIGFPE, floating_point_exception_handler );
}
// initialize the console input (tty mode if wanted and possible)
// warning: might be called from signal handler
tty_err Sys_ConsoleInputInit( void )
{
struct termios tc;
const char* term;
// 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 );
// If SIGCONT is received, reinitialize console
signal( SIGCONT, CON_SigCont );
if ( signal( SIGTSTP, SIG_IGN ) == SIG_DFL )
{
signal( SIGTSTP, CON_SigTStp );
}
stdin_flags = fcntl( STDIN_FILENO, F_GETFL, 0 );
if ( stdin_flags == -1 )
{
stdin_active = qfalse;
return TTY_ERROR;
}
// set non-blocking mode
fcntl( STDIN_FILENO, F_SETFL, stdin_flags | O_NONBLOCK );
stdin_active = qtrue;
// FIXME TTimo initialize this in Sys_Init or something?
if ( !ttycon || !ttycon->integer )
{
ttycon_on = qfalse;
return TTY_DISABLED;
}
term = getenv( "TERM" );
if ( isatty( STDIN_FILENO ) != 1 || !term || !strcmp( term, "dumb" ) || !strcmp( term, "raw" ) )
{
ttycon_on = qfalse;
return TTY_ERROR;
}
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 );
if ( ttycon_ansicolor && ttycon_ansicolor->integer )
{
ttycon_color_on = qtrue;
}
ttycon_on = qtrue;
tty_Hide();
tty_Show();
return TTY_ENABLED;
}
char *Sys_ConsoleInput( void )
{
// we use this when sending back commands
static char text[ sizeof( tty_con.buffer ) ];
int avail;
char key;
char *s;
field_t history;
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';
tty_Back();
}
return NULL;
}
// check if this is a control char
if (key && key < ' ')
{
if (key == '\n')
{
// push it in history
Con_SaveField( &tty_con );
s = tty_con.buffer;
while ( *s == '\\' || *s == '/' ) // skip leading slashes
s++;
Q_strncpyz( text, s, sizeof( text ) );
Field_Clear( &tty_con );
write( STDOUT_FILENO, "\n]", 2 );
return text;
}
if (key == '\t')
{
tty_Hide();
Field_AutoComplete( &tty_con );
tty_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':
if ( Con_HistoryGetPrev( &history ) )
{
tty_Hide();
tty_con = history;
tty_Show();
}
tty_FlushIn();
return NULL;
break;
case 'B':
if ( Con_HistoryGetNext( &history ) )
{
tty_Hide();
tty_con = history;
tty_Show();
}
tty_FlushIn();
return NULL;
break;
case 'C': // right
case 'D': // left
//case 'H': // home
//case 'F': // end
return NULL;
}
}
}
}
if ( key == 12 ) // clear teaminal
{
write( STDOUT_FILENO, "\ec]", 3 );
if ( tty_con.cursor )
{
write( STDOUT_FILENO, tty_con.buffer, tty_con.cursor );
}
tty_FlushIn();
return NULL;
}
Com_DPrintf( "dropping ISCTL sequence: %d, tty_erase: %d\n", key, tty_erase );
tty_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++;
// print the current line (this is differential)
write( STDOUT_FILENO, &key, 1 );
}
return NULL;
}
else if ( stdin_active && com_dedicated->integer )
{
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!
{
fcntl( STDIN_FILENO, F_SETFL, stdin_flags );
stdin_active = qfalse;
return NULL;
}
if ( len < 1 )
return NULL;
text[len-1] = '\0'; // rip off the /n and terminate
s = text;
while ( *s == '\\' || *s == '/' ) // skip leading slashes
s++;
return s;
}
return NULL;
}
/*
=================
Sys_SendKeyEvents
Platform-dependent event handling
=================
*/
void Sys_SendKeyEvents( void )
{
#ifndef DEDICATED
HandleEvents();
#endif
}
/*
==================
Sys_Sleep
Block execution for msec or until input is received.
==================
*/
void Sys_Sleep( int msec ) {
struct timeval timeout;
fd_set fdset;
int res;
//if ( msec == 0 )
// return;
if ( msec < 0 ) {
// special case: wait for console input or network packet
if ( stdin_active ) {
msec = 300;
do {
FD_ZERO( &fdset );
FD_SET( STDIN_FILENO, &fdset );
timeout.tv_sec = msec / 1000;
timeout.tv_usec = (msec % 1000) * 1000;
res = select( STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout );
} while ( res == 0 && NET_Sleep( 10 * 1000 ) );
} else {
// can happen only if no map loaded
// which means we totally stuck as stdin is also disabled :P
//usleep( 300 * 1000 );
while ( NET_Sleep( 3000 * 1000 ) )
;
}
return;
}
#if 1
usleep( msec * 1000 );
#else
if ( com_dedicated->integer && stdin_active ) {
FD_ZERO( &fdset );
FD_SET( STDIN_FILENO, &fdset );
timeout.tv_sec = msec / 1000;
timeout.tv_usec = (msec % 1000) * 1000;
select( STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout );
} else {
usleep( msec * 1000 );
}
#endif
}
static const struct Q3ToAnsiColorTable_s
{
const char Q3color;
const char *ANSIcolor;
} tty_colorTable[ ] =
{
{ COLOR_BLACK, "30" },
{ COLOR_RED, "31" },
{ COLOR_GREEN, "32" },
{ COLOR_YELLOW, "33" },
{ COLOR_BLUE, "34" },
{ COLOR_CYAN, "36" },
{ COLOR_MAGENTA, "35" },
{ COLOR_WHITE, "0" }
};
static const char *getANSIcolor( char Q3color ) {
int i;
for ( i = 0; i < ARRAY_LEN( tty_colorTable ); i++ ) {
if ( Q3color == tty_colorTable[ i ].Q3color ) {
return tty_colorTable[ i ].ANSIcolor;
}
}
return NULL;
}
static qboolean printableChar( char c ) {
if ( ( c >= ' ' && c <= '~' ) || c == '\n' || c == '\r' || c == '\t' )
return qtrue;
else
return qfalse;
}
void Sys_ANSIColorify( const char *msg, char *buffer, int bufferSize )
{
int msgLength;
int i;
char tempBuffer[ 8 ];
const char *ANSIcolor;
if ( !msg || !buffer )
return;
msgLength = strlen( msg );
i = 0;
buffer[ 0 ] = '\0';
while ( i < msgLength )
{
if ( msg[ i ] == '\n' )
{
Com_sprintf( tempBuffer, sizeof( tempBuffer ), "%c[0m\n", 0x1B );
strncat( buffer, tempBuffer, bufferSize - 1 );
i += 1;
}
else if ( msg[ i ] == Q_COLOR_ESCAPE && ( ANSIcolor = getANSIcolor( msg[ i+1 ] ) ) != NULL )
{
Com_sprintf( tempBuffer, sizeof( tempBuffer ), "%c[%sm", 0x1B, ANSIcolor );
strncat( buffer, tempBuffer, bufferSize - 1 );
i += 2;
}
else
{
if ( printableChar( msg[ i ] ) ) {
Com_sprintf( tempBuffer, sizeof( tempBuffer ), "%c", msg[ i ] );
strncat( buffer, tempBuffer, bufferSize - 1 );
}
i += 1;
}
}
}
void Sys_Print( const char *msg )
{
char printmsg[ MAXPRINTMSG ];
size_t len;
if ( ttycon_on )
{
tty_Hide();
}
if ( ttycon_on && ttycon_color_on )
{
Sys_ANSIColorify( msg, printmsg, sizeof( printmsg ) );
len = strlen( printmsg );
}
else
{
char *out = printmsg;
while ( *msg != '\0' && out < printmsg + sizeof( printmsg ) )
{
if ( printableChar( *msg ) )
*out++ = *msg;
msg++;
}
len = out - printmsg;
}
write( STDERR_FILENO, printmsg, len );
if ( ttycon_on )
{
tty_Show();
}
}
void QDECL Sys_SetStatus( const char *format, ... )
{
return;
}
void Sys_ConfigureFPU( void ) // bk001213 - divide by zero
{
#ifdef __linux__
#ifdef __i386
#ifdef __GLIBC__
#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 // __GLIBC__
#endif // __i386
#endif // __linux
}
void Sys_PrintBinVersion( const char* name )
{
const char *date = __DATE__;
const char *time = __TIME__;
const char *sep = "==============================================================";
fprintf( stdout, "\n\n%s\n", sep );
#ifdef DEDICATED
fprintf( stdout, "Linux Quake3 Dedicated Server [%s %s]\n", date, time );
#else
fprintf( stdout, "Linux Quake3 Full Executable [%s %s]\n", date, time );
#endif
fprintf( stdout, " local install: %s\n", name );
fprintf( stdout, "%s\n\n", sep );
}
/*
=================
Sys_BinName
This resolves any symlinks to the binary. It's disabled for debug
builds because there are situations where you are likely to want
to symlink to binaries and /not/ have the links resolved.
=================
*/
#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif
const char *Sys_BinName( const char *arg0 )
{
static char dst[ PATH_MAX ];
#ifdef NDEBUG
#if defined (__linux__)
int n = readlink( "/proc/self/exe", dst, PATH_MAX - 1 );
if ( n >= 0 && n < PATH_MAX )
dst[ n ] = '\0';
else
Q_strncpyz( dst, arg0, PATH_MAX );
#elif defined (__APPLE__)
uint32_t bufsize = sizeof( dst );
if ( _NSGetExecutablePath( dst, &bufsize ) == -1 )
{
Q_strncpyz( dst, arg0, PATH_MAX );
}
#else
#warning Sys_BinName not implemented
Q_strncpyz( dst, arg0, PATH_MAX );
#endif
#else // DEBUG
Q_strncpyz( dst, arg0, PATH_MAX );
#endif
return dst;
}
int Sys_ParseArgs( int argc, const char* argv[] )
{
if ( argc == 2 )
{
if ( ( !strcmp( argv[1], "--version" ) ) || ( !strcmp( argv[1], "-v" ) ) )
{
Sys_PrintBinVersion( Sys_BinName( argv[0] ) );
return 1;
}
}
return 0;
}
int main( int argc, const char* argv[] )
{
char con_title[ MAX_CVAR_VALUE_STRING ];
int xpos, ypos;
//qboolean useXYpos;
char *cmdline;
int len, i;
tty_err err;
#ifdef __APPLE__
// This is passed if we are launched by double-clicking
if ( argc >= 2 && Q_strncmp( argv[1], "-psn", 4 ) == 0 )
argc = 1;
#endif
if ( Sys_ParseArgs( argc, argv ) ) // added this for support
return 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] );
}
/*useXYpos = */ Com_EarlyParseCmdLine( cmdline, con_title, sizeof( con_title ), &xpos, &ypos );
// bk000306 - clear queues
// memset( &eventQue[0], 0, sizeof( eventQue ) );
// memset( &sys_packetReceived[0], 0, sizeof( sys_packetReceived ) );
// get the initial time base
Sys_Milliseconds();
Com_Init( cmdline );
NET_Init();
Com_Printf( "Working directory: %s\n", Sys_Pwd() );
// Sys_ConsoleInputInit() might be called in signal handler
// so modify/init any cvars here
ttycon = Cvar_Get( "ttycon", "1", 0 );
Cvar_SetDescription(ttycon, "Enable access to input/output console terminal.");
ttycon_ansicolor = Cvar_Get( "ttycon_ansicolor", "0", CVAR_ARCHIVE );
Cvar_SetDescription(ttycon_ansicolor, "Convert in-game color codes to ANSI color codes in console terminal.");
err = Sys_ConsoleInputInit();
if ( err == TTY_ENABLED )
{
Com_Printf( "Started tty console (use +set ttycon 0 to disable)\n" );
}
else
{
if ( err == TTY_ERROR )
{
Com_Printf( "stdin is not a tty, tty console mode failed\n" );
Cvar_Set( "ttycon", "0" );
}
}
#ifdef DEDICATED
// init here for dedicated, as we don't have GLimp_Init
InitSig();
#endif
while (1)
{
#ifdef __linux__
Sys_ConfigureFPU();
#endif
#ifdef DEDICATED
// run the game
Com_Frame( qfalse );
#else
// check for other input devices
IN_Frame();
// run the game
Com_Frame( CL_NoDelay() );
#endif
}
// never gets here
return 0;
}