//========= 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;
			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: 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);
	}
}