/* =========================================================================== 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 =========================================================================== */ #include "sys_local.h" #include #include #include #include #ifdef WIN32 #include #endif #include #include #include #include #include #include #include #include #include #include #include "lua.hpp" #include "sol.hpp" #ifndef DEDICATED #ifdef USE_LOCAL_HEADERS # include "SDL.h" # include "SDL_cpuinfo.h" #else # include # include #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; }