/*
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/<XXX>/
=====================
*/
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 <map>\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 <directory>\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 <directory>\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 <userid>\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 <userid>\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
}

