summaryrefslogtreecommitdiff
path: root/engine/sv_remoteaccess.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/sv_remoteaccess.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'engine/sv_remoteaccess.cpp')
-rw-r--r--engine/sv_remoteaccess.cpp933
1 files changed, 933 insertions, 0 deletions
diff --git a/engine/sv_remoteaccess.cpp b/engine/sv_remoteaccess.cpp
new file mode 100644
index 0000000..7d4f7a9
--- /dev/null
+++ b/engine/sv_remoteaccess.cpp
@@ -0,0 +1,933 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Handles all the functions for implementing remote access to the engine
+//
+//===========================================================================//
+
+#include "server_pch.h"
+#include "iclient.h"
+#include "net.h"
+#include "utlbuffer.h"
+#include "utllinkedlist.h"
+#include "igameserverdata.h"
+#include "sv_remoteaccess.h"
+#include "sv_rcon.h"
+#include "sv_filter.h"
+#include "sys.h"
+#include "vprof_engine.h"
+#include "PlayerState.h"
+#include "sv_log.h"
+#ifndef SWDS
+#include "zip/XZip.h"
+#endif
+#include "cl_main.h"
+
+extern IServerGameDLL *serverGameDLL;
+
+CServerRemoteAccess g_ServerRemoteAccess;
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerRemoteAccess, IGameServerData, GAMESERVERDATA_INTERFACE_VERSION, g_ServerRemoteAccess);
+
+ConVar sv_rcon_log( "sv_rcon_log", "1", 0, "Enable/disable rcon logging." );
+
+//-----------------------------------------------------------------------------
+// Host_Stats_f - prints out interesting stats about the server...
+//-----------------------------------------------------------------------------
+void Host_Stats_f (void)
+{
+ char stats[512];
+ g_ServerRemoteAccess.GetStatsString(stats, sizeof(stats));
+ ConMsg("CPU In_(KB/s) Out_(KB/s) Uptime Map_changes FPS Players Connects\n%s\n", stats);
+}
+static ConCommand stats("stats", Host_Stats_f, "Prints server performance variables" );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CServerRemoteAccess::CServerRemoteAccess()
+{
+ m_iBytesSent = 0;
+ m_iBytesReceived = 0;
+ m_NextListenerID = 0;
+ m_AdminUIID = INVALID_LISTENER_ID;
+ m_nScreenshotListener = -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: unique id to associate data transfers with sessions
+//-----------------------------------------------------------------------------
+ra_listener_id CServerRemoteAccess::GetNextListenerID( bool authConnection, const netadr_t *adr )
+{
+ int i = m_ListenerIDs.AddToTail();
+ m_ListenerIDs[i].listenerID = i;
+ m_ListenerIDs[i].authenticated = !authConnection;
+ m_ListenerIDs[i].m_bHasAddress = ( adr != NULL );
+ if ( adr )
+ {
+ m_ListenerIDs[i].adr = *adr;
+ }
+ return i;
+}
+
+
+bool GetStringHelper( CUtlBuffer & cmd, char *outBuf, int bufSize )
+{
+ outBuf[0] = 0;
+ cmd.GetStringManualCharCount( outBuf, bufSize );
+ if ( !cmd.IsValid() )
+ {
+ cmd.Purge();
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: handles a request
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::WriteDataRequest( CRConServer *pNetworkListener, ra_listener_id listener, const void *buffer, int bufferSize)
+{
+ m_iBytesReceived += bufferSize;
+ // ConMsg("RemoteAccess: bytes received: %d\n", m_iBytesReceived);
+
+ if ( bufferSize < 2*sizeof(int) ) // check that the buffer contains at least the id and type
+ {
+ return;
+ }
+
+ CUtlBuffer cmd(buffer, bufferSize, CUtlBuffer::READ_ONLY);
+ bool invalidRequest = false;
+
+ while ( invalidRequest == false && (int)cmd.TellGet() < (int)(cmd.Size() - 2 * sizeof(int) ) ) // while there is commands to read
+ {
+ // parse out the buffer
+ int requestID = cmd.GetInt();
+ pNetworkListener->SetRequestID( listener, requestID ); // tell the rcon server the ID so it can reflect it when the console redirect flushes
+ int requestType = cmd.GetInt();
+
+ switch (requestType)
+ {
+ case SERVERDATA_REQUESTVALUE:
+ {
+ if ( IsAuthenticated(listener) )
+ {
+ char variable[256];
+ if ( !GetStringHelper( cmd, variable, sizeof(variable) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ RequestValue( listener, requestID, variable);
+ if ( !GetStringHelper( cmd, variable, sizeof(variable) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ }
+ else
+ {
+ char variable[256];
+ if ( !GetStringHelper( cmd, variable, sizeof(variable) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ if ( !GetStringHelper( cmd, variable, sizeof(variable) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ }
+ }
+ break;
+
+ case SERVERDATA_SETVALUE:
+ {
+ if ( IsAuthenticated(listener) )
+ {
+ char variable[256];
+ char value[256];
+ if ( !GetStringHelper( cmd, variable, sizeof(variable) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ if ( !GetStringHelper( cmd, value, sizeof(value) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ SetValue(variable, value);
+ }
+ else
+ {
+ char command[512];
+ if ( !GetStringHelper( cmd, command, sizeof(command) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ if ( !GetStringHelper( cmd, command, sizeof(command) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ }
+ }
+ break;
+
+ case SERVERDATA_EXECCOMMAND:
+ {
+ if ( IsAuthenticated(listener) )
+ {
+ char command[512];
+ if ( !GetStringHelper( cmd, command, sizeof(command) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+
+ ExecCommand(command);
+
+ if ( listener != m_AdminUIID )
+ {
+ LogCommand( listener, va( "command \"%s\"", command) );
+ }
+ if ( !GetStringHelper( cmd, command, sizeof(command) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ }
+ else
+ {
+ char command[512];
+ if ( !GetStringHelper( cmd, command, sizeof(command) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ if ( !GetStringHelper( cmd, command, sizeof(command) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ LogCommand( listener, "Bad Password" );
+ }
+ }
+ break;
+
+ case SERVERDATA_AUTH:
+ {
+ char password[512];
+ if ( !GetStringHelper( cmd, password, sizeof(password) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ CheckPassword( pNetworkListener, listener, requestID, password );
+ if ( !GetStringHelper( cmd, password, sizeof(password) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+
+ if ( m_ListenerIDs[ listener ].authenticated )
+ {
+ // if the second string has a non-zero value, it is a userid.
+ int userID = atoi( password );
+ const ConCommandBase *var = g_pCVar->GetCommands();
+ while ( var )
+ {
+ if ( var->IsCommand() )
+ {
+ if ( Q_stricmp( var->GetName(), "mp_disable_autokick" ) == 0 )
+ {
+ Cbuf_AddText( va( "mp_disable_autokick %d\n", userID ) );
+ Cbuf_Execute();
+ break;
+ }
+ }
+ var = var->GetNext();
+ }
+ }
+ }
+ break;
+
+ case SERVERDATA_TAKE_SCREENSHOT:
+#ifndef SWDS
+ m_nScreenshotListener = listener;
+ CL_TakeJpeg( );
+#endif
+ break;
+
+ case SERVERDATA_SEND_CONSOLE_LOG:
+ {
+#ifndef SWDS
+ CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
+ if ( GetConsoleLogFileData( buf ) )
+ {
+ HZIP hZip = CreateZipZ( 0, 1024 * 1024, ZIP_MEMORY );
+ void *pMem;
+ unsigned long nLen;
+ ZipAdd( hZip, "console.log", buf.Base(), buf.TellMaxPut(), ZIP_MEMORY );
+ ZipGetMemory( hZip, &pMem, &nLen );
+ SendResponseToClient( listener, SERVERDATA_CONSOLE_LOG_RESPONSE, pMem, nLen );
+ CloseZip( hZip );
+ }
+ else
+ {
+ LogCommand( listener, "Failed to read console log!\n" );
+ RespondString( listener, requestID, "Failed to read console log!\n" );
+ }
+#endif
+ }
+ break;
+
+#ifdef VPROF_ENABLED
+ case SERVERDATA_VPROF:
+ {
+ char password[25];
+ if ( !GetStringHelper( cmd, password, sizeof(password) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ if ( !GetStringHelper( cmd, password, sizeof(password) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ if ( IsAuthenticated(listener) )
+ {
+ RegisterVProfDataListener( listener );
+ LogCommand( listener, "Remote VProf started!\n" );
+ RespondString( listener, requestID, "Remote VProf started!\n" );
+ }
+ }
+ break;
+
+ case SERVERDATA_REMOVE_VPROF:
+ {
+ char password[25];
+ if ( !GetStringHelper( cmd, password, sizeof(password) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ if ( !GetStringHelper( cmd, password, sizeof(password) ) )
+ {
+ invalidRequest = true;
+ break;
+ }
+ if ( IsAuthenticated(listener) )
+ {
+ RemoveVProfDataListener( listener );
+ LogCommand( listener, "Remote VProf finished!\n" );
+ RespondString( listener, requestID, "Remote VProf finished!\n" );
+ }
+ }
+ break;
+#endif
+
+ default:
+ Assert(!("Unknown requestType in CServerRemoteAccess::WriteDataRequest()"));
+ cmd.Purge();
+ invalidRequest = true;
+ break;
+ };
+ }
+}
+
+// NOTE: This version is used by the server DLL or server plugins
+void CServerRemoteAccess::WriteDataRequest( ra_listener_id listener, const void *buffer, int bufferSize )
+{
+ WriteDataRequest( &RCONServer(), listener, buffer, bufferSize );
+}
+
+
+//-----------------------------------------------------------------------------
+// Uploads a screenshot to a particular listener
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::UploadScreenshot( const char *pFileName )
+{
+#ifndef SWDS
+ if ( m_nScreenshotListener < 0 )
+ return;
+
+ CUtlBuffer buf( 128 * 1024, 0 );
+ if ( g_pFullFileSystem->ReadFile( pFileName, "MOD", buf ) )
+ {
+ HZIP hZip = CreateZipZ( 0, 1024 * 1024, ZIP_MEMORY );
+ void *pMem;
+ unsigned long nLen;
+ ZipAdd( hZip, "screenshot.jpg", buf.Base(), buf.TellMaxPut(), ZIP_MEMORY );
+ ZipGetMemory( hZip, &pMem, &nLen );
+ SendResponseToClient( m_nScreenshotListener, SERVERDATA_SCREENSHOT_RESPONSE, pMem, nLen );
+ CloseZip( hZip );
+ }
+ else
+ {
+ LogCommand( m_nScreenshotListener, "Failed to read screenshot!\n" );
+ RespondString( m_nScreenshotListener, 0, "Failed to read screenshot!\n" );
+ }
+
+ m_nScreenshotListener = -1;
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: log information about a command that ran
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::LogCommand( ra_listener_id listener, const char *msg )
+{
+ if ( !sv_rcon_log.GetBool() )
+ return;
+
+ if ( listener < (ra_listener_id)m_ListenerIDs.Count() && m_ListenerIDs[listener].m_bHasAddress )
+ {
+ Log( "rcon from \"%s\": %s\n", m_ListenerIDs[listener].adr.ToString(), msg );
+ }
+ else
+ {
+ Log( "rcon from \"unknown\": %s\n", msg );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: checks if this user has provided the correct password
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::CheckPassword( CRConServer *pNetworkListener, ra_listener_id listener, int requestID, const char *password )
+{
+ // If the pw does not match, then not authed
+ if ( !pNetworkListener->IsPassword( password ) )
+ {
+ BadPassword( pNetworkListener, listener );
+ return;
+ }
+
+ // allocate a spot in the list for the response
+ int i = m_ResponsePackets.AddToTail();
+ m_ResponsePackets[i].responderID = listener; // record who we need to respond to
+
+ CUtlBuffer &response = m_ResponsePackets[i].packet;
+
+ // build the response
+ response.PutInt(requestID);
+ response.PutInt(SERVERDATA_AUTH_RESPONSE);
+ response.PutString("");
+ response.PutString("");
+
+ m_ListenerIDs[ listener ].authenticated = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: returns true if this connection has provided the correct password
+//-----------------------------------------------------------------------------
+bool CServerRemoteAccess::IsAuthenticated( ra_listener_id listener )
+{
+ // Checking for >= 0 is tautological because ra_listener_id is unsigned
+ Assert( /*listener >= 0 &&*/ listener < (ra_listener_id)m_ListenerIDs.Count() );
+ return m_ListenerIDs[listener].authenticated;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: send a bad password packet
+// Returns TRUE if socket was closed
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::BadPassword( CRConServer *pNetworkListener, ra_listener_id listener )
+{
+ ListenerStore_t& listenerStore = m_ListenerIDs[listener];
+
+ listenerStore.authenticated = false;
+
+ if ( pNetworkListener->HandleFailedRconAuth( listenerStore.adr ) )
+ {
+ // Close the socket if too many failed attempts
+ pNetworkListener->BCloseAcceptedSocket( listener );
+ }
+ else
+ {
+ //
+ // Respond to the rcon user
+ //
+
+ // allocate a spot in the list for the response
+ int i = m_ResponsePackets.AddToTail();
+ m_ResponsePackets[i].responderID = listener; // record who we need to respond to
+ CUtlBuffer &response = m_ResponsePackets[i].packet;
+
+ // build the response
+ response.PutInt(-1); // special flag for bad password
+ response.PutInt(SERVERDATA_AUTH_RESPONSE);
+ response.PutString("");
+ response.PutString("");
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: returns the number of bytes read
+//-----------------------------------------------------------------------------
+int CServerRemoteAccess::GetDataResponseSize( ra_listener_id listener )
+{
+ for( int i = m_ResponsePackets.Head(); m_ResponsePackets.IsValidIndex(i); i = m_ResponsePackets.Next(i) )
+ {
+ // copy response into buffer
+ if ( m_ResponsePackets[i].responderID != listener ) // not for us, skip to the next entry
+ continue;
+
+ CUtlBuffer &response = m_ResponsePackets[i].packet;
+ return response.TellPut();
+ }
+ return 0;
+}
+
+int CServerRemoteAccess::ReadDataResponse( ra_listener_id listener, void *buffer, int bufferSize )
+{
+ for( int i = m_ResponsePackets.Head(); m_ResponsePackets.IsValidIndex(i); i = m_ResponsePackets.Next(i) )
+ {
+ // copy response into buffer
+ if ( m_ResponsePackets[i].responderID != listener ) // not for us, skip to the next entry
+ continue;
+
+ CUtlBuffer &response = m_ResponsePackets[i].packet;
+ int bytesToCopy = response.TellPut();
+ Assert(bufferSize >= bytesToCopy);
+ if (bytesToCopy <= bufferSize)
+ {
+ memcpy(buffer, response.Base(), bytesToCopy);
+ }
+ else
+ {
+ // not enough room in buffer, don't return message
+ bytesToCopy = 0;
+ }
+
+ m_iBytesSent += bytesToCopy;
+ // ConMsg("RemoteAccess: bytes sent: %d\n", m_iBytesSent);
+
+ // remove from list
+ m_ResponsePackets.Remove(i);
+ // return bytes copied
+ return bytesToCopy;
+ }
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: looks up a cvar and posts a return value
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::RequestValue( ra_listener_id listener, int requestID, const char *variable)
+{
+ // look up the cvar
+ CUtlBuffer value(0, 256, CUtlBuffer::TEXT_BUFFER); // text-mode buffer
+ LookupValue(variable, value);
+
+ // allocate a spot in the list for the response
+ int i = m_ResponsePackets.AddToTail();
+ m_ResponsePackets[i].responderID = listener; // record who we need to respond to
+
+ CUtlBuffer &response = m_ResponsePackets[i].packet;
+
+ // build the response
+ response.PutInt(requestID);
+ response.PutInt(SERVERDATA_RESPONSE_VALUE);
+ response.PutString(variable);
+
+ //Assert(value.TellPut() > 0);
+ response.PutInt(value.TellPut());
+ if (value.TellPut())
+ {
+ response.Put(value.Base(), value.TellPut());
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: looks up a cvar and posts a return value
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::RespondString( ra_listener_id listener, int requestID, const char *pString )
+{
+ // allocate a spot in the list for the response
+ int i = m_ResponsePackets.AddToTail();
+ m_ResponsePackets[i].responderID = listener; // record who we need to respond to
+
+ CUtlBuffer &response = m_ResponsePackets[i].packet;
+
+ // build the response
+ response.PutInt(requestID);
+ response.PutInt(SERVERDATA_RESPONSE_STRING);
+ response.PutString(pString);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets a cvar or value
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::SetValue(const char *variable, const char *value)
+{
+ // check for special types
+ if (!stricmp(variable, "map"))
+ {
+ // push a map change command
+ Cbuf_AddText( va( "changelevel %s\n", value ) );
+ Cbuf_Execute();
+ }
+ else if (!stricmp(variable, "mapcycle"))
+ {
+ // write out a new mapcycle file
+ ConVarRef mapcycle( "mapcyclefile" );
+ if ( mapcycle.IsValid() )
+ {
+ FileHandle_t f = g_pFileSystem->Open(mapcycle.GetString(), "wt");
+ if (!f)
+ {
+ // mapcycle file probably read only, fall pack to temporary file
+ Msg("Couldn't write to read-only file %s, using file _temp_mapcycle.txt instead.\n", mapcycle.GetString());
+ mapcycle.SetValue("_temp_mapcycle.txt" );
+ f = g_pFileSystem->Open(mapcycle.GetString(), "wt");
+ if (!f)
+ {
+ return;
+ }
+ }
+ g_pFileSystem->Write(value, Q_strlen(value) + 1, f);
+ g_pFileSystem->Close(f);
+ }
+ }
+ else
+ {
+ // Stick the cvar set in the command string, so client notification, replication, etc happens
+ Cbuf_AddText( va("%s %s", variable, value) );
+ Cbuf_AddText("\n");
+ Cbuf_Execute();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: execs a command
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::ExecCommand(const char *cmdString)
+{
+ Cbuf_AddText((char *)cmdString);
+ Cbuf_AddText("\n");
+ Cbuf_Execute();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the value of a particular server variable
+//-----------------------------------------------------------------------------
+bool CServerRemoteAccess::LookupValue(const char *variable, CUtlBuffer &value)
+{
+ Assert(value.IsText());
+
+ // first see if it's a cvar
+ const char *strval = LookupStringValue(variable);
+ if (strval)
+ {
+ value.PutString(strval);
+ value.PutChar(0);
+ }
+ else if (!stricmp(variable, "stats"))
+ {
+ char szStats[512];
+ GetStatsString( szStats, sizeof( szStats ) );
+ value.PutString( szStats );
+ value.PutChar(0);
+ }
+ else if (!stricmp(variable, "banlist"))
+ {
+ // returns a list of banned users and ip's
+ GetUserBanList(value);
+ }
+ else if (!stricmp(variable, "playerlist"))
+ {
+ GetPlayerList(value);
+ }
+ else if (!stricmp(variable, "maplist"))
+ {
+ GetMapList(value);
+ }
+ else if (!stricmp(variable, "uptime"))
+ {
+ int timeSeconds = (int)(Plat_FloatTime());
+ value.PutInt(timeSeconds);
+ value.PutChar(0);
+ }
+ else if (!stricmp(variable, "ipaddress"))
+ {
+ char addr[25];
+ Q_snprintf( addr, sizeof(addr), "%s:%i", net_local_adr.ToString(true), sv.GetUDPPort());
+ value.PutString( addr );
+ value.PutChar(0);
+ }
+ else if (!stricmp(variable, "mapcycle"))
+ {
+ ConVarRef mapcycle( "mapcyclefile" );
+ if ( mapcycle.IsValid() )
+ {
+ // send the mapcycle list file
+ FileHandle_t f = g_pFileSystem->Open(mapcycle.GetString(), "rb" );
+
+ if ( f == FILESYSTEM_INVALID_HANDLE )
+ return true;
+
+ int len = g_pFileSystem->Size(f);
+ char *mapcycleData = (char *)_alloca( len+1 );
+ if ( len && g_pFileSystem->Read( mapcycleData, len, f ) )
+ {
+ mapcycleData[len] = 0; // Make sure it's null terminated.
+ value.PutString((const char *)mapcycleData);
+ value.PutChar(0);
+ }
+ else
+ {
+ value.PutString( "" );
+ value.PutChar(0);
+ }
+
+ g_pFileSystem->Close( f );
+
+
+ }
+ }
+ else
+ {
+ // value not found, null terminate
+ value.PutChar(0);
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the value of a particular server variable for simple string values
+//-----------------------------------------------------------------------------
+const char *CServerRemoteAccess::LookupStringValue(const char *variable)
+{
+ static char s_ReturnBuf[32];
+ IConVar *pVar = g_pCVar->FindVar( variable );
+ if ( pVar )
+ {
+ ConVarRef var( pVar );
+ if ( var.IsValid() )
+ return var.GetString();
+ }
+
+ // special types
+ if ( !Q_stricmp( variable, "map" ) )
+ return sv.GetMapName();
+
+ if ( !Q_stricmp( variable, "playercount" ) )
+ {
+ Q_snprintf( s_ReturnBuf, sizeof(s_ReturnBuf) - 1, "%d", sv.GetNumClients() - sv.GetNumProxies());
+ return s_ReturnBuf;
+ }
+
+ if ( !Q_stricmp( variable, "maxplayers" ) )
+ {
+ Q_snprintf( s_ReturnBuf, sizeof(s_ReturnBuf) - 1, "%d", sv.GetMaxClients() );
+ return s_ReturnBuf;
+ }
+
+ if ( !Q_stricmp( variable, "gamedescription" ) && serverGameDLL )
+ return serverGameDLL->GetGameDescription();
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: fills a buffer with a list of all banned IP addresses
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::GetUserBanList(CUtlBuffer &value)
+{
+ // add user bans
+ int i;
+ for (i = 0; i < g_UserFilters.Count(); i++)
+ {
+ value.Printf("%i %s : %.3f min\n", i + 1, GetUserIDString(g_UserFilters[i].userid), g_UserFilters[i].banTime);
+ }
+
+ // add ip filters
+ for (i = 0; i < g_IPFilters.Count() ; i++)
+ {
+ unsigned char b[4];
+ *(unsigned *)b = g_IPFilters[i].compare;
+ value.Printf("%i %i.%i.%i.%i : %.3f min\n", i + 1 + g_UserFilters.Count(), b[0], b[1], b[2], b[3], g_IPFilters[i].banTime);
+ }
+
+ value.PutChar(0);
+}
+
+void CServerRemoteAccess::GetStatsString(char *buf, int bufSize)
+{
+ float avgIn=0,avgOut=0;
+
+ sv.GetNetStats( avgIn, avgOut );
+
+ // format: CPU percent, Bandwidth in, Bandwidth out, uptime, changelevels, framerate, total players
+ _snprintf(buf, bufSize - 1, "%-6.2f %-10.2f %-11.2f %-7i %-12i %-8.2f %-8i %-8i",
+ sv.GetCPUUsage() * 100,
+ avgIn / 1024.0f,
+ avgOut / 1024.0f,
+ (int)(Sys_FloatTime()) / 60,
+ sv.GetSpawnCount() - 1,
+ 1.0/host_frametime, // frame rate
+ sv.GetNumClients() - sv.GetNumProxies(),
+ sv.GetNumConnections());
+ buf[bufSize - 1] = 0;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Fills buffer with details on everyone in the server
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::GetPlayerList(CUtlBuffer &value)
+{
+ if ( !serverGameClients )
+ {
+ return;
+ }
+
+ for ( int i=0 ; i< sv.GetClientCount() ; i++ )
+ {
+ CGameClient *client = sv.Client(i);
+ if ( !client || !client->IsActive() )
+ continue;
+
+ CPlayerState *pl = serverGameClients->GetPlayerState( client->edict );
+ if ( !pl )
+ continue;
+
+ // valid user, add to buffer
+ // format per user, each user seperated by a newline '\n'
+ // "name authID ipAddress ping loss frags time"
+ if ( client->IsFakeClient() )
+ {
+ value.Printf("\"%s\" %s 0 0 0 %d 0\n",
+ client->GetClientName(),
+ client->GetNetworkIDString(),
+ pl->frags);
+ }
+ else
+ {
+ value.Printf("\"%s\" %s %s %d %d %d %d\n",
+ client->GetClientName(),
+ client->GetNetworkIDString(),
+ client->GetNetChannel()->GetAddress(),
+ (int)(client->GetNetChannel()->GetAvgLatency(FLOW_OUTGOING) * 1000.0f),
+ (int)(client->GetNetChannel()->GetAvgLoss(FLOW_INCOMING)),
+ pl->frags,
+ (int)(client->GetNetChannel()->GetTimeConnected()));
+ }
+ }
+
+ value.PutChar(0);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fills buffer with list of maps from this mod
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::GetMapList(CUtlBuffer &value)
+{
+ // search the directory structure.
+ char mapwild[MAX_QPATH];
+ char friendly_com_gamedir[ MAX_OSPATH ];
+ strcpy(mapwild, "maps/*.bsp");
+ Q_strncpy( friendly_com_gamedir, com_gamedir, sizeof(friendly_com_gamedir) );
+ Q_strlower( friendly_com_gamedir );
+
+ char const *findfn = Sys_FindFirst( mapwild, NULL, 0 );
+ while ( findfn )
+ {
+ char curDir[MAX_PATH];
+ _snprintf(curDir, MAX_PATH, "maps/%s", findfn);
+ g_pFileSystem->GetLocalPath(curDir, curDir, MAX_PATH);
+
+ // limit maps displayed to ones for the mod only
+ if (strstr(curDir, friendly_com_gamedir))
+ {
+ // clean up the map name
+ char mapName[MAX_PATH];
+ strcpy(mapName, findfn);
+ char *extension = strstr(mapName, ".bsp");
+ if (extension)
+ {
+ *extension = 0;
+ }
+
+ // write into buffer
+ value.PutString(mapName);
+ value.PutString("\n");
+ }
+ findfn = Sys_FindNext( NULL, 0 );
+ }
+
+ Sys_FindClose();
+ value.PutChar(0);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: sends a message to all the watching admin UI's
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::SendMessageToAdminUI( ra_listener_id listenerID, const char *message)
+{
+ if ( listenerID != m_AdminUIID )
+ {
+ Warning( "ServerRemoteAccess: Sending AdminUI message to non-AdminUI listener\n" );
+ }
+
+ // allocate a spot in the list for the response
+ int i = m_ResponsePackets.AddToTail();
+ m_ResponsePackets[i].responderID = listenerID; // record who we need to respond to
+ CUtlBuffer &response = m_ResponsePackets[i].packet;
+
+ // post the message
+ response.PutInt(0);
+ response.PutInt(SERVERDATA_UPDATE);
+ response.PutString(message);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sends a response to the client
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::SendResponseToClient( ra_listener_id listenerID, ServerDataResponseType_t type, void *pData, int nDataLen )
+{
+ // allocate a spot in the list for the response
+ int i = m_ResponsePackets.AddToTail();
+ m_ResponsePackets[i].responderID = listenerID; // record who we need to respond to
+ CUtlBuffer &response = m_ResponsePackets[i].packet;
+
+ // post the message
+ response.PutInt( 0 );
+ response.PutInt( type );
+ response.PutInt( nDataLen );
+ response.Put( pData, nDataLen );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: sends an opaque blob of data from VProf to a remote rcon listener
+//-----------------------------------------------------------------------------
+void CServerRemoteAccess::SendVProfData( ra_listener_id listenerID, bool bGroupData, void *data, int len )
+{
+ Assert( listenerID != m_AdminUIID ); // only RCON clients support this right now
+ SendResponseToClient( listenerID, bGroupData ? SERVERDATA_VPROF_GROUPS : SERVERDATA_VPROF_DATA, data, len );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: C function for rest of engine to access CServerRemoteAccess class
+//-----------------------------------------------------------------------------
+extern "C" void NotifyDedicatedServerUI(const char *message)
+{
+ if ( g_ServerRemoteAccess.GetAdminUIID() != INVALID_LISTENER_ID ) // if we have an admin UI actually registered
+ {
+ g_ServerRemoteAccess.SendMessageToAdminUI( g_ServerRemoteAccess.GetAdminUIID(), message);
+ }
+}