You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
933 lines
27 KiB
933 lines
27 KiB
//========= 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); |
|
} |
|
}
|
|
|