/* Copyright (C) 1997-2001 Id Software, Inc. 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "server.h" #include "../qcommon/webdownload.h" /* =============================================================================== OPERATOR CONSOLE ONLY COMMANDS These commands can only be entered from stdin or by a remote operator datagram =============================================================================== */ /* ================== SV_SetPlayer Sets sv_client and sv_player to the player with idnum Cmd_Argv(1) ================== */ qboolean SV_SetPlayer( void ) { client_t *cl; int i; int idnum; char *s; if( Cmd_Argc() < 2 ) return qfalse; s = Cmd_Argv(1); // numeric values are just slot numbers if( s[0] >= '0' && s[0] <= '9' ) { idnum = atoi( Cmd_Argv(1) ); if( idnum < 0 || idnum >= sv_maxclients->integer ) { Com_Printf( "Bad client slot: %i\n", idnum ); return qfalse; } sv_client = &svs.clients[idnum]; sv_player = sv_client->edict; if( !sv_client->state ) { Com_Printf( "Client %i is not active\n", idnum ); return qfalse; } return qtrue; } // check for a name match for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( !cl->state ) continue; if( !Q_stricmp(cl->name, s) ) { sv_client = cl; sv_player = sv_client->edict; return qtrue; } } Com_Printf( "Userid %s is not on the server\n", s ); return qfalse; } /* =============================================================================== SAVEGAME FILES =============================================================================== */ /* ===================== SV_WipeSavegame Delete save// ===================== */ void SV_WipeSavegame( char *savename ) { char name[MAX_OSPATH]; char *s; Com_DPrintf( "SV_WipeSaveGame(%s)\n", savename ); Q_snprintfz( name, sizeof(name), "%s/save/%s/server.ssv", FS_Gamedir(), savename ); FS_RemoveFile(name); Q_snprintfz( name, sizeof(name), "%s/save/%s/game.ssv", FS_Gamedir(), savename ); FS_RemoveFile(name); Q_snprintfz( name, sizeof(name), "%s/save/%s/*.sav", FS_Gamedir(), savename ); s = Sys_FindFirst( name, 0, 0 ); while( s ) { FS_RemoveFile(s); s = Sys_FindNext( 0, 0 ); } Sys_FindClose(); Q_snprintfz( name, sizeof(name), "%s/save/%s/*.sv2", FS_Gamedir(), savename ); s = Sys_FindFirst(name, 0, 0 ); while( s ) { FS_RemoveFile( s ); s = Sys_FindNext( 0, 0 ); } Sys_FindClose(); } /* ================ SV_CopySaveGame ================ */ void SV_CopySaveGame( char *src, char *dst ) { char name[MAX_OSPATH], name2[MAX_OSPATH]; int l, len; char *found; Com_DPrintf( "SV_CopySaveGame(%s, %s)\n", src, dst ); SV_WipeSavegame(dst); // copy the savegame over Q_snprintfz( name, sizeof(name), "%s/save/%s/server.ssv", FS_Gamedir(), src ); Q_snprintfz( name2, sizeof(name2), "%s/save/%s/server.ssv", FS_Gamedir(), dst ); FS_CopyFile( name, name2 ); Q_snprintfz( name, sizeof(name), "%s/save/%s/game.ssv", FS_Gamedir(), src ); Q_snprintfz( name2, sizeof(name2), "%s/save/%s/game.ssv", FS_Gamedir(), dst ); FS_CopyFile( name, name2 ); Q_snprintfz( name, sizeof(name), "%s/save/%s/", FS_Gamedir(), src ); len = (int)strlen(name); Q_snprintfz(name, sizeof(name), "%s/save/%s/*.sav", FS_Gamedir(), src ); found = Sys_FindFirst( name, 0, 0 ); while( found ) { Q_strncpyz( name+len, found+len, sizeof(name)-len ); Q_snprintfz( name2, sizeof(name2), "%s/save/%s/%s", FS_Gamedir(), dst, found+len ); FS_CopyFile( name, name2 ); // change sav to sv2 l = (int)strlen(name); Q_strncpyz( name+l-3, "sv2", sizeof(name)-(l-3) ); l = (int)strlen(name2); Q_strncpyz( name2+l-3, "sv2", sizeof(name)-(l-3) ); FS_CopyFile( name, name2 ); found = Sys_FindNext( 0, 0 ); } Sys_FindClose(); } /* ============== SV_WriteLevelFile ============== */ void SV_WriteLevelFile( void ) { char name[MAX_QPATH]; int file; Com_DPrintf( "SV_WriteLevelFile()\n" ); Q_snprintfz( name, sizeof(name), "save/current/%s.sv2", sv.name ); if( FS_FOpenFile( name, &file, FS_WRITE ) == -1 ) { Com_Printf( "Failed to open %s\n", name ); return; } FS_Write( sv.configstrings, sizeof(sv.configstrings), file ); CM_WritePortalState( file ); FS_FCloseFile( file ); Q_snprintfz( name, sizeof(name), "save/current/%s.sav", sv.name ); ge->WriteLevel( name ); } /* ============== SV_ReadLevelFile ============== */ void SV_ReadLevelFile( void ) { char name[MAX_OSPATH]; int file; Com_DPrintf( "SV_ReadLevelFile()\n" ); Q_snprintfz( name, sizeof(name), "save/current/%s.sv2", sv.name ); if( FS_FOpenFile( name, &file, FS_READ ) == -1 ) { Com_Printf( "Failed to open %s\n", name ); return; } FS_Read( sv.configstrings, sizeof(sv.configstrings), file ); CM_ReadPortalState( file ); FS_FCloseFile( file ); Q_snprintfz( name, sizeof(name), "save/current/%s.sav", sv.name ); ge->ReadLevel( name ); } /* ============== SV_WriteServerFile ============== */ void SV_WriteServerFile( qboolean autosave ) { int file; cvar_t *var; char name[MAX_OSPATH], string[128]; char comment[32]; time_t aclock; struct tm *newtime; Com_DPrintf( "SV_WriteServerFile(%s)\n", autosave ? "true" : "false" ); Q_strncpyz( name, "save/current/server.ssv", sizeof(name) ); if( FS_FOpenFile( name, &file, FS_WRITE ) == -1 ) { Com_Printf( "Couldn't write %s\n", name ); return; } // write the comment field memset( comment, 0, sizeof(comment) ); if( !autosave ) { time( &aclock ); newtime = localtime( &aclock ); Q_snprintfz( comment, sizeof(comment), "%2i:%i%i %2i/%2i ", newtime->tm_hour, newtime->tm_min/10, newtime->tm_min%10, newtime->tm_mon+1, newtime->tm_mday); Q_strncatz( comment, sv.name, sizeof(comment)-1-strlen(comment) ); } else { // autosaved Q_snprintfz( comment, sizeof(comment), "%s", sv.name ); } FS_Write( comment, sizeof(comment), file ); // write the mapcmd FS_Write( svs.mapcmd, sizeof(svs.mapcmd), file ); // write all CVAR_LATCH cvars for( var = cvar_vars ; var ; var=var->next ) { if( !(var->flags & CVAR_LATCH) ) continue; if( strlen(var->name) >= sizeof(name)-1 || strlen(var->string) >= sizeof(string)-1 ) { Com_Printf( "Cvar too long: %s = %s\n", var->name, var->string ); continue; } memset( name, 0, sizeof(name) ); memset( string, 0, sizeof(string) ); Q_strncpyz( name, var->name, sizeof(name) ); Q_strncpyz( string, var->string, sizeof(string) ); FS_Write( name, sizeof(name), file ); FS_Write( string, sizeof(string), file ); } FS_FCloseFile( file ); // write game state ge->WriteGame( "save/current/game.ssv", autosave ); } /* ============== SV_ReadServerFile ============== */ void SV_ReadServerFile( void ) { int file; char name[MAX_OSPATH], string[128]; char comment[32]; char mapcmd[MAX_TOKEN_CHARS]; Com_DPrintf( "SV_ReadServerFile()\n" ); Q_snprintfz( name, sizeof(name), "save/current/server.ssv" ); if( FS_FOpenFile( name, &file, FS_READ ) == -1 ) { Com_Printf( "Couldn't read %s\n", name ); return; } // read the comment field FS_Read( comment, sizeof(comment), file ); // read the mapcmd FS_Read( mapcmd, sizeof(mapcmd), file ); // read all CVAR_LATCH cvars while( 1 ) { if( !FS_Read( name, sizeof(name), file ) ) break; FS_Read( string, sizeof(string), file ); Com_DPrintf( "Set %s = %s\n", name, string ); Cvar_ForceSet( name, string ); } FS_FCloseFile( file ); // start a new game fresh with new cvars SV_InitGame(); Q_strncpyz( svs.mapcmd, mapcmd, sizeof(svs.mapcmd) ); // read game state ge->ReadGame( "save/current/game.ssv" ); } //========================================================= #ifdef SERVER_DOWNLOAD_COMMAND /* ===================== SV_WebDownloadProgress Callback function for webdownloads. ===================== */ int SV_WebDownloadProgress( double percent ) { Com_Printf("Download progress: %02.02f\n", 100.0f*percent); return 0; } void SV_Download_f( void ) { qboolean success; char tmpn[MAX_OSPATH]; char newn[MAX_OSPATH]; char url[MAX_STRING_CHARS]; char referer[MAX_STRING_CHARS]; if(Cmd_Argc()!=3) { Com_Printf("SV_Download_f expect 2 arguments: download url filename\n"); Com_Printf("Example: download http://foo.bar foo.pk3\n"); Com_Printf("will download http://foo.bar/foo.pk3 into basewsw/foo.pk3\n"); return; } Q_snprintfz(url, sizeof(url), "%s/%s", Cmd_Argv(1), Cmd_Argv(2)); Q_snprintfz(tmpn, sizeof(tmpn), "%s/%s", FS_Gamedir(), "sv_temp.tmp"); Q_snprintfz(referer, sizeof(referer), "wsw://localhost" ); Com_Printf( "Server Web download: %s from %s\n", tmpn, url ); success=Web_Get(url, referer, tmpn, 0, SV_WebDownloadProgress); if(!success) { Com_Printf("Server Web download Error.\n"); return; } Com_Printf("Server Web download Success.\n"); // final name for downloaded file Q_snprintfz( newn, sizeof(newn), "%s/%s", FS_Gamedir(), Cmd_Argv(2)); // rename the old one if it exists FS_RenameFile( newn, va("%s_%d.save",newn, Sys_Milliseconds() ) ); // rename the downloaded file if( FS_RenameFile( tmpn, newn ) ) { Com_Printf( "Failed to rename the downloaded file.\n" ); return; } // we know need to update the file system // problem the old file is still in the FS structure // so we cannot just add the new pak to the FS // so we restart the server SV_Shutdown("Updating Maps files\n", qtrue); } #endif /* ================== SV_GameMap_f Saves the state of the map just being exited and goes to a new map. If the initial character of the map string is '*', the next map is in a new unit, so the current savegame directory is cleared of map files. Example: *inter.cin+jail Clears the archived maps, plays the inter.cin cinematic, then goes to map jail.bsp. ================== */ void SV_GameMap_f( void ) { char *map; int i; client_t *cl; qboolean *savedInuse; if( Cmd_Argc() != 2 ) { Com_Printf( "USAGE: gamemap \n" ); return; } // if not a pcx, demo, or cinematic, check to make sure the level exists map = Cmd_Argv(1); if( !strstr (map, ".") ) { char filename[MAX_QPATH], expanded[MAX_QPATH]; Q_strncpyz( filename, map, sizeof(filename) ); COM_DefaultExtension( filename, ".bsp", sizeof(filename) ); Q_snprintfz( expanded, sizeof(expanded), "maps/%s", filename ); if( FS_FOpenFile (expanded, NULL, FS_READ) == -1 ) { Com_Printf( "Can't find %s\n", expanded ); return; } } Com_DPrintf( "SV_GameMap(%s)\n", map ); // remove all bots before changing map if( svs.clients ) { // clients array might not be initialized the first time for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( cl->state && cl->edict && (cl->edict->r.svflags & SVF_FAKECLIENT) ) { SV_DropClient( cl, DROP_TYPE_GENERAL, "" ); } } } FS_CreatePath( va("%s/save/current/", FS_Gamedir()) ); // check for clearing the current savegame if( map[0] == '*' ) { // wipe all the *.sav files SV_WipeSavegame( "current" ); } else { // save the map just exited if( sv.state == ss_game ) { // clear all the client inuse flags before saving so that // when the level is re-entered, the clients will spawn // at spawn points instead of occupying body shells savedInuse = Mem_TempMalloc( sv_maxclients->integer * sizeof(qboolean) ); for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { savedInuse[i] = cl->edict->r.inuse; cl->edict->r.inuse = qfalse; } SV_WriteLevelFile(); // we must restore these for clients to transfer over correctly for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) cl->edict->r.inuse = savedInuse[i]; Mem_TempFree( savedInuse ); } } // start up the next map SV_Map( Cmd_Argv(1), qfalse, !Q_stricmp(Cmd_Argv(0), "devmap") ); // archive server state Q_strncpyz( svs.mapcmd, Cmd_Argv(1), sizeof(svs.mapcmd) ); // copy off the level to the autosave slot if( !dedicated->integer ) { SV_WriteServerFile( qtrue ); SV_CopySaveGame( "current", "save0" ); } } /* ================== SV_Map_f Goes directly to a given map without any savegame archiving. For development work ================== */ void SV_Map_f( void ) { char *map; char filename[MAX_QPATH], expanded[MAX_QPATH]; // if not a pcx, demo, or cinematic, check to make sure the level exists map = Cmd_Argv(1); if( !strstr (map, ".") ) { Q_strncpyz( filename, map, sizeof(filename) ); COM_DefaultExtension( filename, ".bsp", sizeof(filename) ); Q_snprintfz( expanded, sizeof(expanded), "maps/%s", filename ); if( FS_FOpenFile (expanded, NULL, FS_READ) == -1 ) { Com_Printf( "Can't find %s\n", expanded ); return; } } sv.state = ss_dead; // don't save current level when changing SV_WipeSavegame( "current" ); SV_GameMap_f(); } /* ===================================================================== SAVEGAMES ===================================================================== */ /* ============== SV_Loadgame_f ============== */ void SV_Loadgame_f( void ) { char name[MAX_OSPATH]; char *dir; if( Cmd_Argc() != 2 ) { Com_Printf( "USAGE: loadgame \n" ); return; } Com_Printf( "Loading game...\n" ); dir = Cmd_Argv(1); if( strstr( dir, ".." ) || strstr( dir, "/" ) || strstr( dir, "\\" ) ) { Com_Printf( "Bad savedir.\n" ); } // make sure the server.ssv file exists Q_snprintfz( name, sizeof(name), "save/%s/server.ssv", Cmd_Argv(1) ); if( FS_FOpenFile( name, NULL, FS_READ ) == -1 ) { Com_Printf( "No such savegame: %s\n", name ); return; } SV_CopySaveGame( Cmd_Argv(1), "current" ); SV_ReadServerFile(); // go to the map sv.state = ss_dead; // don't save current level when changing SV_Map( svs.mapcmd, qtrue, qfalse ); } /* ============== SV_Savegame_f ============== */ void SV_Savegame_f( void ) { char *dir; if( sv.state != ss_game ) { Com_Printf( "You must be in a game to save.\n" ); return; } if( Cmd_Argc() != 2 ) { Com_Printf( "USAGE: savegame \n" ); return; } /* if (Cvar_VariableValue("deathmatch")) { Com_Printf ("Can't savegame in a deathmatch\n"); return; }*/ if( !Q_stricmp(Cmd_Argv(1), "current") ) { Com_Printf( "Can't save to 'current'\n" ); return; } /*if( sv_maxclients->integer == 1 && svs.clients[0].edict->client->r.health <= 0 ) { Com_Printf( "\nCan't savegame while dead!\n" ); return; }*/ dir = Cmd_Argv(1); if( strstr (dir, "..") || strstr (dir, "/") || strstr (dir, "\\") ) { Com_Printf( "Bad savedir.\n" ); } Com_Printf( "Saving game...\n" ); // archive current level, including all client edicts. // when the level is reloaded, they will be shells awaiting // a connecting client SV_WriteLevelFile(); // save server state SV_WriteServerFile( qfalse ); // copy it off SV_CopySaveGame( "current", dir ); Com_Printf( "Done.\n" ); } //=============================================================== /* ================== SV_Kick_f Kick a user off of the server ================== */ void SV_Kick_f( void ) { if( !svs.initialized ) { Com_Printf( "No server running.\n" ); return; } if( Cmd_Argc() != 2 ) { Com_Printf( "Usage: kick \n" ); return; } if( !SV_SetPlayer() ) return; // wsw : r1q2: ignore kick message on connecting players (and those with no name) if( sv_client->state < CS_CONNECTED || !sv_client->name[0] ) return; SV_ClientPrintf( NULL, "%s^7 was kicked\n", sv_client->name ); SV_DropClient( sv_client, DROP_TYPE_NORECONNECT, "You were kicked" ); sv_client->lastPacketReceivedTime = svs.realtime; // min case there is a funny zombie } /* ================ SV_Status_f ================ */ void SV_Status_f( void ) { int i, j, l; client_t *cl; char *s; int ping; if( !svs.clients ) { Com_Printf( "No server running.\n" ); return; } Com_Printf( "map : %s\n", sv.name ); Com_Printf( "num score ping name lastmsg address qport rate \n" ); Com_Printf( "--- ----- ---- --------------- ------- --------------------- ------ ------\n" ); for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( !cl->state ) continue; Com_Printf( "%3i ", i ); Com_Printf( "%5i ", cl->edict->r.client->r.frags ); if( cl->state == CS_CONNECTED ) Com_Printf( "CNCT " ); else if ( cl->state == CS_ZOMBIE ) Com_Printf( "ZMBI " ); else if ( cl->state == CS_AWAITING ) Com_Printf( "AWAI " ); else { ping = cl->ping < 9999 ? cl->ping : 9999; Com_Printf( "%4i ", ping ); } Com_Printf( "%s", cl->name ); l = 16 - (int)strlen(cl->name); for( j = 0; j < l; j++ ) Com_Printf(" "); Com_Printf( "%7i ", svs.realtime - cl->lastPacketReceivedTime ); s = NET_AdrToString( &cl->netchan.remoteAddress ); Com_Printf( "%s", s ); l = 22 - (int)strlen(s); for( j = 0; j < l; j++ ) Com_Printf(" "); Com_Printf( "%5i", cl->netchan.qport ); #ifndef RATEKILLED // wsw : jal : print real rate in use Com_Printf(" "); if( cl->edict && (cl->edict->r.svflags & SVF_FAKECLIENT) ) Com_Printf( "BOT" ); else if( cl->rate == 99999 ) Com_Printf( "LAN" ); else Com_Printf( "%5i", cl->rate ); #endif Com_Printf("\n"); } Com_Printf("\n"); } /* ================== SV_ConSay_f ================== */ void SV_ConSay_f( void ) { char *p; char text[1024]; if( Cmd_Argc () < 2 ) return; Q_strncpyz( text, "console: ", sizeof(text) ); p = Cmd_Args(); if( *p == '"' ) { p++; p[strlen(p)-1] = 0; } Q_strncatz( text, p, sizeof(text) ); SV_ClientChatf( NULL, "%s\n", text ); } /* ================== SV_Heartbeat_f ================== */ void SV_Heartbeat_f( void ) { svc.last_heartbeat = 0; } /* =========== SV_Serverinfo_f Examine or change the serverinfo string =========== */ void SV_Serverinfo_f( void ) { Com_Printf( "Server info settings:\n" ); Info_Print( Cvar_Serverinfo() ); } /* =========== SV_DumpUser_f Examine all a users info strings =========== */ void SV_DumpUser_f( void ) { if( Cmd_Argc() != 2 ) { Com_Printf( "Usage: info \n" ); return; } if( !SV_SetPlayer() ) return; Com_Printf( "userinfo\n" ); Com_Printf( "--------\n" ); Info_Print( sv_client->userinfo ); } /* =============== SV_KillServer_f Kick everyone off, possibly in preparation for a new game =============== */ void SV_KillServer_f( void ) { if( !svs.initialized ) return; SV_Shutdown( "Server was killed.\n", qfalse ); NET_Config( qfalse ); // close network sockets } /* =============== SV_ServerCommand_f Let the game dll handle a command =============== */ void SV_ServerCommand_f( void ) { if( !ge ) { Com_Printf( "No game loaded.\n" ); return; } ge->ServerCommand(); } //=========================================================== /* ================== SV_InitOperatorCommands ================== */ void SV_InitOperatorCommands( void ) { Cmd_AddCommand( "heartbeat", SV_Heartbeat_f ); Cmd_AddCommand( "kick", SV_Kick_f ); Cmd_AddCommand( "status", SV_Status_f ); Cmd_AddCommand( "serverinfo", SV_Serverinfo_f ); Cmd_AddCommand( "dumpuser", SV_DumpUser_f ); Cmd_AddCommand( "map", SV_Map_f ); Cmd_AddCommand( "devmap", SV_Map_f ); Cmd_AddCommand( "gamemap", SV_GameMap_f ); Cmd_AddCommand( "save", SV_Savegame_f ); Cmd_AddCommand( "load", SV_Loadgame_f ); Cmd_AddCommand( "killserver", SV_KillServer_f ); Cmd_AddCommand( "sv", SV_ServerCommand_f ); if( dedicated->integer ) { Cmd_AddCommand( "say", SV_ConSay_f ); #ifdef SERVER_DOWNLOAD_COMMAND Cmd_AddCommand( "download", SV_Download_f ); #endif } #ifdef SERVERSIDE_DEMOS Cmd_AddCommand( "serverrecord", SV_ServerRecord_f ); Cmd_AddCommand( "serverstop", SV_ServerStop_f ); #endif }