diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/sv_remoteaccess.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'engine/sv_remoteaccess.cpp')
| -rw-r--r-- | engine/sv_remoteaccess.cpp | 933 |
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); + } +} |