//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: steam state machine that handles authenticating steam users
//
//===========================================================================//

#ifdef _WIN32
#if !defined( _X360 )
#include "winlite.h"
#include <winsock2.h> // INADDR_ANY defn
#endif
#elif POSIX
#include <netinet/in.h>
#endif

#include "sv_steamauth.h"
#include "sv_filter.h"
#include "inetchannel.h"
#include "netadr.h"
#include "server.h"
#include "proto_oob.h"
#include "host.h"
#include "tier0/vcrmode.h"
#include "sv_plugin.h"
#include "sv_log.h"
#include "filesystem_engine.h"
#include "filesystem_init.h"
#include "tier0/icommandline.h"
#include "steam/steam_gameserver.h"
#include "hltvserver.h"
#include "sys_dll.h"
#if defined( REPLAY_ENABLED )
#include "replayserver.h"
#endif

extern ConVar sv_lan;
extern ConVar sv_visiblemaxplayers;
extern ConVar sv_region;
extern ConVar tv_enable;

static void sv_setsteamblockingcheck_f( IConVar *pConVar, const char *pOldString, float flOldValue );

ConVar  sv_steamblockingcheck( "sv_steamblockingcheck", "0", 0, 
							  "Check each new player for Steam blocking compatibility, 1 = message only, 2 >= drop if any member of owning clan blocks,"
							  "3 >= drop if any player has blocked, 4 >= drop if player has blocked anyone on server", sv_setsteamblockingcheck_f );

#if defined( _X360 )
#include "xbox/xbox_win32stubs.h"
#endif

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#pragma warning( disable: 4355 ) // disables ' 'this' : used in base member initializer list'

ConVar sv_master_share_game_socket( "sv_master_share_game_socket", "1", 0, 
	"Use the game's socket to communicate to the master server. "
	"If this is 0, then it will create a socket on -steamport + 1 "
	"to communicate to the master server on." );

static char s_szTempMsgBuf[16000];

static void MsgAndLog( const char *fmt, ... )
{
	va_list ap;
	va_start(ap, fmt);
	V_vsprintf_safe( s_szTempMsgBuf, fmt, ap );

	// Does Log always print to the console?
	//if ( !engine->IsDedicatedServer() )
	//	Msg("%s", s_szTempMsgBuf );
	Log("%s", s_szTempMsgBuf );
}

static void WarningAndLog( const char *fmt, ... )
{
	va_list ap;
	va_start(ap, fmt);
	V_vsprintf_safe( s_szTempMsgBuf, fmt, ap );

	// Does Log always print to the console?
	Warning("%s", s_szTempMsgBuf );
	Log("%s", s_szTempMsgBuf );
}


//-----------------------------------------------------------------------------
// Purpose: singleton accessor
//-----------------------------------------------------------------------------
static CSteam3Server s_Steam3Server;
CSteam3Server  &Steam3Server()
{
	return s_Steam3Server;
}


//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CSteam3Server::CSteam3Server() 
#if !defined(NO_STEAM)
:
	m_CallbackLogonSuccess( this, &CSteam3Server::OnLogonSuccess ),
	m_CallbackLogonFailure( this, &CSteam3Server::OnLogonFailure ),
	m_CallbackLoggedOff( this, &CSteam3Server::OnLoggedOff ),
	m_CallbackValidateAuthTicketResponse( this, &CSteam3Server::OnValidateAuthTicketResponse ),
	m_CallbackPlayerCompatibilityResponse( this, &CSteam3Server::OnComputeNewPlayerCompatibilityResponse ),
	m_CallbackGSPolicyResponse( this, &CSteam3Server::OnGSPolicyResponse )
#endif
{
	m_bHasActivePlayers = false;
	m_bLogOnResult = false;
	m_eServerMode = eServerModeInvalid;
	m_eServerType = eServerTypeNormal;
    m_bWantsSecure = false;		// default to insecure currently, this may change
    m_bInitialized = false;
	m_bWantsPersistentAccountLogon = false;
	m_bLogOnFinished = false;
	m_bMasterServerUpdaterSharingGameSocket = false;
	m_steamIDLanOnly.InstancedSet( 0,0, k_EUniversePublic, k_EAccountTypeInvalid );
	m_SteamIDGS.InstancedSet( 1, 0, k_EUniverseInvalid, k_EAccountTypeInvalid );
	m_QueryPort = 0;
}

//-----------------------------------------------------------------------------
// Purpose: detect current server mode based on cvars & settings
//-----------------------------------------------------------------------------
EServerMode CSteam3Server::GetCurrentServerMode()
{
	if ( sv_lan.GetBool() )
	{
		return eServerModeNoAuthentication;
	}
	else if ( CommandLine()->FindParm( "-insecure" ) )
	{
		return eServerModeAuthentication;
	}
	else 
	{
		return eServerModeAuthenticationAndSecure;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CSteam3Server::~CSteam3Server()
{
	Shutdown();
}


void CSteam3Server::Activate( EServerType serverType )
{
	// we are active, check if sv_lan changed or we're trying to change server type
	if ( GetCurrentServerMode() == m_eServerMode && m_eServerType == serverType )
	{
		// we are active and LANmode/servertype didnt change. done.
		return;
	}

	if ( BIsActive() )
	{
		// shut down before we change server mode
		Shutdown();
	}

	m_unIP = INADDR_ANY;
	m_usPort = 26900;

	if ( CommandLine()->FindParm( "-steamport" ) )
	{
		m_usPort = CommandLine()->ParmValue( "-steamport", 26900 );
	}

	ConVarRef ipname( "ip" );
	if ( ipname.IsValid() )
	{
		netadr_t ipaddr;
		NET_StringToAdr( ipname.GetString(), &ipaddr );
		if ( !ipaddr.IsLoopback() && !ipaddr.IsLocalhost() )
		{
			m_unIP = ipaddr.GetIPHostByteOrder();
		}
	}

	m_eServerMode = GetCurrentServerMode();
	m_eServerType = serverType;

	char gamedir[MAX_OSPATH];
	Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) );

	// Figure out the game port. If we're doing a SrcTV relay, then ignore the NS_SERVER port and don't tell Steam that we have a game server.
	uint16 usGamePort = 0;
	if ( serverType == eServerTypeNormal )
	{
		usGamePort = NET_GetUDPPort( NS_SERVER );
	}

	uint16 usMasterServerUpdaterPort;
	if ( sv_master_share_game_socket.GetBool() )
	{
		m_bMasterServerUpdaterSharingGameSocket = true;
		usMasterServerUpdaterPort = MASTERSERVERUPDATERPORT_USEGAMESOCKETSHARE;
		if ( serverType == eServerTypeTVRelay )
			m_QueryPort = NET_GetUDPPort( NS_HLTV );
		else
			m_QueryPort = usGamePort;
	}
	else
	{
		m_bMasterServerUpdaterSharingGameSocket = false;
		usMasterServerUpdaterPort = m_usPort;
		m_QueryPort = m_usPort;
	}
#ifndef _X360

	switch ( m_eServerMode )
	{
		case eServerModeNoAuthentication:
			MsgAndLog( "Initializing Steam libraries for LAN server\n" );
			break;
		case eServerModeAuthentication:
			MsgAndLog( "Initializing Steam libraries for INSECURE Internet server.  Authentication and VAC not requested.\n" );
			break;
		case eServerModeAuthenticationAndSecure:
			MsgAndLog( "Initializing Steam libraries for secure Internet server\n" );
			break;
		default:
			WarningAndLog( "Bogus eServermode %d!\n", m_eServerMode );
			Assert( !"Bogus server mode?!" );
			break;
	}

	SteamAPI_SetTryCatchCallbacks( false ); // We don't use exceptions, so tell steam not to use try/catch in callback handlers
	if ( CommandLine()->FindParm("-hushsteam") || !SteamGameServer_InitSafe(
			m_unIP,
			m_usPort+1,	// Steam lives on -steamport + 1, master server updater lives on -steamport.
			usGamePort,
			usMasterServerUpdaterPort,
			m_eServerMode,
			GetSteamInfIDVersionInfo().szVersionString ) )
	{
steam_no_good:
#if !defined( NO_STEAM )
		WarningAndLog( "*********************************************************\n" );
		WarningAndLog( "*\tUnable to load Steam support library.*\n" );
		WarningAndLog( "*\tThis server will operate in LAN mode only.*\n" );
		WarningAndLog( "*********************************************************\n" );
#endif
		m_eServerMode = eServerModeNoAuthentication;
		sv_lan.SetValue( false );
		return;
	}
	Init(); // Steam API context init
	if ( SteamGameServer() == NULL )
	{
		Assert( false );
		goto steam_no_good;
	}

	// Note that SteamGameServer_InitSafe() calls SteamAPI_SetBreakpadAppID() for you, which is what we don't want if we wish
	// to report crashes under a different AppId. Reset it back to our crashing one now.
	if ( sv.IsDedicated() )
	{
		SteamAPI_SetBreakpadAppID( GetSteamInfIDVersionInfo().ServerAppID );
	}

	// Set some stuff that should NOT change while the server is
	// running
	SteamGameServer()->SetProduct( GetSteamInfIDVersionInfo().szProductString );
	SteamGameServer()->SetGameDescription( serverGameDLL->GetGameDescription() );
	SteamGameServer()->SetDedicatedServer( sv.IsDedicated() );
	SteamGameServer()->SetModDir( gamedir );

	// Use anonymous logon, or persistent?
	if ( m_sAccountToken.IsEmpty() )
	{
		m_bWantsPersistentAccountLogon = false;
		MsgAndLog( "No account token specified; logging into anonymous game server account.  (Use sv_setsteamaccount to login to a persistent account.)\n" );
		SteamGameServer()->LogOnAnonymous();
	}
	else
	{
		m_bWantsPersistentAccountLogon = true;
		MsgAndLog( "Logging into Steam game server account\n" );

		// TODO: Change this to use just the token when the SDK is updated
		SteamGameServer()->LogOn( m_sAccountToken );
	}

#endif

	SendUpdatedServerDetails();
}


//-----------------------------------------------------------------------------
// Purpose: game server stopped, shutdown Steam game server session
//-----------------------------------------------------------------------------
void CSteam3Server::Shutdown()
{
	if ( !BIsActive() )
		return;

	SteamGameServer_Shutdown();
	m_bHasActivePlayers = false;
	m_bLogOnResult = false;
	m_SteamIDGS = k_steamIDNotInitYetGS;
	m_eServerMode = eServerModeInvalid;

	Clear(); // Steam API context shutdown
}


//-----------------------------------------------------------------------------
// Purpose: returns true if the userid's are the same
//-----------------------------------------------------------------------------
bool CSteam3Server::CompareUserID( const USERID_t & id1, const USERID_t & id2 )
{
	if ( id1.idtype != id2.idtype )
		return false;

	switch ( id1.idtype )
	{
	case IDTYPE_STEAM:
	case IDTYPE_VALVE:
		{
			return (id1.steamid == id2.steamid );
		}
	default:
		break;
	}

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: returns true if this userid is already on this server
//-----------------------------------------------------------------------------
bool CSteam3Server::CheckForDuplicateSteamID( const CBaseClient *client )
{
	// in LAN mode we allow reuse of SteamIDs
	if ( BLanOnly() ) 
		return false;

	// Compare connecting client's ID to other IDs on the server
	for ( int i=0 ; i< sv.GetClientCount() ; i++ )
	{
		const IClient *cl = sv.GetClient( i );

		// Not connected, no SteamID yet
		if ( !cl->IsConnected() || cl->IsFakeClient() )
			continue;

		if ( cl->GetNetworkID().idtype != IDTYPE_STEAM )
			continue;

		// don't compare this client against himself in the list
		if ( client == cl )
			continue;

		if ( !CompareUserID( client->GetNetworkID(), cl->GetNetworkID() ) )
			continue;

		// SteamID is reused
		return true;
	}

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: Called when secure policy is set
//-----------------------------------------------------------------------------
const CSteamID &CSteam3Server::GetGSSteamID()
{
	return m_SteamIDGS;
}


#if !defined(NO_STEAM)
//-----------------------------------------------------------------------------
// Purpose: Called when secure policy is set
//-----------------------------------------------------------------------------
void CSteam3Server::OnGSPolicyResponse( GSPolicyResponse_t *pPolicyResponse )
{
	if ( !BIsActive() )
		return;

	if ( SteamGameServer() && SteamGameServer()->BSecure() )
	{
		MsgAndLog( "VAC secure mode is activated.\n" );
	}
	else
	{
		MsgAndLog( "VAC secure mode disabled.\n" );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CSteam3Server::OnLogonSuccess( SteamServersConnected_t *pLogonSuccess )
{
	if ( !BIsActive() )
		return;

	if ( !m_bLogOnResult )
	{
		m_bLogOnResult = true;
	}

	if ( !BLanOnly() )
	{
		MsgAndLog( "Connection to Steam servers successful.\n" );
		if ( SteamGameServer() )
		{
			uint32 ip = SteamGameServer()->GetPublicIP();
			MsgAndLog( "   Public IP is %d.%d.%d.%d.\n", (ip >> 24) & 255, (ip >> 16) & 255, (ip >> 8) & 255, ip & 255 );
		}
	}

	if ( SteamGameServer() )
	{
		m_SteamIDGS = SteamGameServer()->GetSteamID();
		if ( m_SteamIDGS.BAnonGameServerAccount() )
		{
			MsgAndLog( "Assigned anonymous gameserver Steam ID %s.\n", m_SteamIDGS.Render() );
		}
		else if ( m_SteamIDGS.BPersistentGameServerAccount() )
		{
			MsgAndLog( "Assigned persistent gameserver Steam ID %s.\n", m_SteamIDGS.Render() );
		}
		else
		{
			WarningAndLog( "Assigned Steam ID %s, which is of an unexpected type!\n", m_SteamIDGS.Render() );
			Assert( !"Unexpected steam ID type!" );
		}
	}
	else
	{
		m_SteamIDGS = k_steamIDNotInitYetGS;
	}

	// send updated server details
	// OnLogonSuccess() gets called each time we logon, so if we get dropped this gets called
	// again and we get need to retell the AM our details
	SendUpdatedServerDetails();
}


//-----------------------------------------------------------------------------
// Purpose: callback on unable to connect to the steam3 backend
// Input  : eResult - 
//-----------------------------------------------------------------------------
void CSteam3Server::OnLogonFailure( SteamServerConnectFailure_t *pLogonFailure )
{
	if ( !BIsActive() )
		return;

	//bool bRetrying = false;
	if ( !m_bLogOnResult )
	{
		if ( pLogonFailure->m_eResult == k_EResultServiceUnavailable )
		{
			if ( !BLanOnly() )
			{
				MsgAndLog( "Connection to Steam servers successful (SU).\n" );
			}
		}
		else
		{
			// we tried to be in secure mode but failed
			// force into insecure mode
			// eventually change this to set sv_lan as well
			if ( !BLanOnly() )
			{
				WarningAndLog( "Could not establish connection to Steam servers.  (Result = %d)\n", pLogonFailure->m_eResult );

				// If this was a permanent failure, switch to anonymous
				// TODO: Requires SDK update
				/*if ( m_bWantsPersistentAccountLogon && ( pLogonFailure->m_eResult == k_EResultInvalidParam || pLogonFailure->m_eResult == k_EResultAccountNotFound ) )
				{
					WarningAndLog( "Invalid game server account token. Retrying Steam connection with anonymous logon\n" );

					m_bWantsPersistentAccountLogon = false;
					bRetrying = true;
					SteamGameServer()->LogOnAnonymous();
				}*/
			}
		}
	}

	m_bLogOnResult = true;
	//m_bLogOnResult = !bRetrying;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : eResult - 
//-----------------------------------------------------------------------------
void CSteam3Server::OnLoggedOff( SteamServersDisconnected_t *pLoggedOff )
{
	if ( !BLanOnly() )
	{
		WarningAndLog( "Connection to Steam servers lost.  (Result = %d)\n", pLoggedOff->m_eResult );
	}
}

void CSteam3Server::OnComputeNewPlayerCompatibilityResponse( ComputeNewPlayerCompatibilityResult_t *pCompatibilityResult )
{
	CBaseClient *client = ClientFindFromSteamID( pCompatibilityResult->m_SteamIDCandidate );
	if ( !client )
		return;
	if ( sv_steamblockingcheck.GetInt() )
	{
		if ( sv_steamblockingcheck.GetInt() >= 2 )
		{
			if ( pCompatibilityResult->m_cClanPlayersThatDontLikeCandidate > 0 )
			{
				client->Disconnect( "Another player on this server ( member of owning clan ) does not want to play with this player." );
				return;
			}
		}		
		if ( sv_steamblockingcheck.GetInt() >= 3 )
		{
			if ( pCompatibilityResult->m_cPlayersThatDontLikeCandidate > 0 )
			{
				client->Disconnect( "Another player on this server does not want to play with this player." );
				return;
			}
		}
		if ( sv_steamblockingcheck.GetInt() >= 4 )
		{
			if ( pCompatibilityResult->m_cPlayersThatCandidateDoesntLike > 0 )
			{
				client->Disconnect( "Existing player on this server is on this players block list." );
				return;
			}
		}

		if ( pCompatibilityResult->m_cClanPlayersThatDontLikeCandidate > 0 ||
			pCompatibilityResult->m_cPlayersThatDontLikeCandidate > 0 ||
			pCompatibilityResult->m_cPlayersThatCandidateDoesntLike > 0 )
		{
			MsgAndLog( "Player %s is blocked by %d players and %d clan members and has blocked %d players on server\n", client->GetClientName(), 
					pCompatibilityResult->m_cPlayersThatDontLikeCandidate,
					pCompatibilityResult->m_cClanPlayersThatDontLikeCandidate,
					pCompatibilityResult->m_cPlayersThatCandidateDoesntLike	);
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CSteam3Server::OnValidateAuthTicketResponse( ValidateAuthTicketResponse_t *pValidateAuthTicketResponse )
{
	//Msg("Steam backend:Got approval for %x\n", pGSClientApprove->m_SteamID.ConvertToUint64() );
	// We got the approval message from the back end.
    // Note that if we dont get it, we default to approved anyway
	// dont need to send anything back

	if ( !BIsActive() )
		return;

	CBaseClient *client = ClientFindFromSteamID( pValidateAuthTicketResponse->m_SteamID );
	if ( !client )
		return;

	if ( pValidateAuthTicketResponse->m_eAuthSessionResponse != k_EAuthSessionResponseOK )
	{
		OnValidateAuthTicketResponseHelper( client, pValidateAuthTicketResponse->m_eAuthSessionResponse );
		return;
	}

	if ( Filter_IsUserBanned( client->GetNetworkID() ) )
	{
		sv.RejectConnection( client->GetNetChannel()->GetRemoteAddress(), client->GetClientChallenge(), "#GameUI_ServerRejectBanned" );
		client->Disconnect( va( "STEAM UserID %s is banned", client->GetNetworkIDString() ) );
	}
	else if ( CheckForDuplicateSteamID( client ) )
	{
		client->Disconnect(  "STEAM UserID %s is already\nin use on this server", client->GetNetworkIDString() );					
	}
	else
	{
		char msg[ 512 ];
		sprintf( msg, "\"%s<%i><%s><>\" STEAM USERID validated\n", client->GetClientName(), client->GetUserID(), client->GetNetworkIDString() );

		DevMsg( "%s", msg );
		g_Log.Printf( "%s", msg );

		g_pServerPluginHandler->NetworkIDValidated( client->GetClientName(), client->GetNetworkIDString() );

		// Tell IServerGameClients if its version is high enough.
		if ( g_iServerGameClientsVersion >= 4 )
		{
			serverGameClients->NetworkIDValidated( client->GetClientName(), client->GetNetworkIDString() );
		}
	}

	if ( sv_steamblockingcheck.GetInt() >= 1 )
	{
		SteamGameServer()->ComputeNewPlayerCompatibility( pValidateAuthTicketResponse->m_SteamID );
	}
	client->SetFullyAuthenticated();
}


//-----------------------------------------------------------------------------
// Purpose: helper for the two places that deny a user connect
// Input  : steamID - id to kick
//			eDenyReason - reason
//			pchOptionalText - some kicks also have a string with them
//-----------------------------------------------------------------------------
void CSteam3Server::OnValidateAuthTicketResponseHelper( CBaseClient *cl, EAuthSessionResponse eAuthSessionResponse )
{
	INetChannel *netchan = cl->GetNetChannel();

	// If the client is timing out, the Steam failure is probably related (e.g. game crashed). Let's just print that the client timed out.
	if ( netchan && netchan->IsTimingOut() )
	{
		cl->Disconnect( CLIENTNAME_TIMED_OUT, cl->GetClientName() );
		return;
	}

	// Emit a more detailed diagnostic.
	WarningAndLog( "STEAMAUTH: Client %s received failure code %d\n", cl->GetClientName(), (int)eAuthSessionResponse );

	switch ( eAuthSessionResponse )
	{
		case k_EAuthSessionResponseUserNotConnectedToSteam:
			if ( !BLanOnly() ) 
				cl->Disconnect( INVALID_STEAM_LOGON_NOT_CONNECTED );
			break;
		case k_EAuthSessionResponseLoggedInElseWhere:
			if ( !BLanOnly() ) 
				cl->Disconnect( INVALID_STEAM_LOGGED_IN_ELSEWHERE );
			break;
		case k_EAuthSessionResponseNoLicenseOrExpired:
			cl->Disconnect( "This Steam account does not own this game. \nPlease login to the correct Steam account" );
			break;
		case k_EAuthSessionResponseVACBanned:
			if ( !BLanOnly() ) 
				cl->Disconnect( INVALID_STEAM_VACBANSTATE );
			break;
		case k_EAuthSessionResponseAuthTicketCanceled:
			if ( !BLanOnly() ) 
				cl->Disconnect( INVALID_STEAM_LOGON_TICKET_CANCELED );
			break;
		case k_EAuthSessionResponseAuthTicketInvalidAlreadyUsed:
		case k_EAuthSessionResponseAuthTicketInvalid:
			if ( !BLanOnly() ) 
				cl->Disconnect( INVALID_STEAM_TICKET );
			break;
		case k_EAuthSessionResponseVACCheckTimedOut:
			cl->Disconnect( "An issue with your computer is blocking the VAC system. You cannot play on secure servers.\n\nhttps://support.steampowered.com/kb_article.php?ref=2117-ILZV-2837" );
			break;
		default:
			cl->Disconnect( "Client dropped by server" );
			break;
	}
}
#endif

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : steamIDFind - 
// Output : IClient
//-----------------------------------------------------------------------------
CBaseClient *CSteam3Server::ClientFindFromSteamID( CSteamID & steamIDFind )
{
	for ( int i=0 ; i< sv.GetClientCount() ; i++ )
	{
		CBaseClient *cl = (CBaseClient *)sv.GetClient( i );

		// Not connected, no SteamID yet
		if ( !cl->IsConnected() || cl->IsFakeClient() )
			continue;

		if ( cl->GetNetworkID().idtype != IDTYPE_STEAM )
			continue;

		USERID_t id = cl->GetNetworkID();
		if (id.steamid == steamIDFind )
		{
			return cl;
		}
	}
	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: tell Steam that a new user connected
//-----------------------------------------------------------------------------
bool CSteam3Server::NotifyClientConnect( CBaseClient *client, uint32 unUserID, netadr_t & adr, const void *pvCookie, uint32 ucbCookie )
{
	if ( !BIsActive() ) 
		return true;

	if ( !client || client->IsFakeClient() )
		return false;

	// Make sure their ticket is long enough
	if ( ucbCookie <= sizeof(uint64) )
	{
		WarningAndLog("Client UserID %x connected with invalid ticket size %d\n", unUserID, ucbCookie );
		return false;
	}

	// steamID is prepended to the ticket
	CUtlBuffer buffer( pvCookie, ucbCookie, CUtlBuffer::READ_ONLY );
	uint64 ulSteamID = buffer.GetInt64();

	CSteamID steamID( ulSteamID );
	if ( steamID.GetEUniverse() != SteamGameServer()->GetSteamID().GetEUniverse() )
	{
		WarningAndLog("Client %d %s connected to universe %d, but game server %s is running in universe %d\n", unUserID, steamID.Render(),
			steamID.GetEUniverse(), SteamGameServer()->GetSteamID().Render(), SteamGameServer()->GetSteamID().GetEUniverse() );
		return false;
	}
	if ( !steamID.IsValid() || !steamID.BIndividualAccount() )
	{
		WarningAndLog("Client %d connected from %s with invalid Steam ID %s\n", unUserID, adr.ToString(), steamID.Render() );
		return false;
	}

	// skip the steamID
	pvCookie = (uint8 *)pvCookie + sizeof( uint64 );
	ucbCookie -= sizeof( uint64 );
	EBeginAuthSessionResult eResult = SteamGameServer()->BeginAuthSession( pvCookie, ucbCookie, steamID );
	switch ( eResult )
	{
	case k_EBeginAuthSessionResultOK:
		//Msg("S3: BeginAuthSession request for %x was good.\n", steamID.ConvertToUint64( ) );
		break;
	case k_EBeginAuthSessionResultInvalidTicket:
		WarningAndLog("S3: Client connected with invalid ticket: UserID: %x\n", unUserID );
		return false;
	case k_EBeginAuthSessionResultDuplicateRequest:
		WarningAndLog("S3: Duplicate client connection: UserID: %x SteamID %x\n", unUserID, steamID.ConvertToUint64( ) );
		return false;
	case k_EBeginAuthSessionResultInvalidVersion:
		WarningAndLog("S3: Client connected with invalid ticket ( old version ): UserID: %x\n", unUserID );
		return false;
	case k_EBeginAuthSessionResultGameMismatch:
		// This error would be very useful to present to the client.
		WarningAndLog("S3: Client connected with ticket for the wrong game: UserID: %x\n", unUserID );
		return false;
	case k_EBeginAuthSessionResultExpiredTicket:
		WarningAndLog("S3: Client connected with expired ticket: UserID: %x\n", unUserID );
		return false;
	default:
		WarningAndLog("S3: Client failed auth session for unknown reason. UserID: %x\n", unUserID );
		return false;
	}

	// first checks ok, we know now the SteamID
	client->SetSteamID( steamID );

	SendUpdatedServerDetails();

	return true;
}

bool CSteam3Server::NotifyLocalClientConnect( CBaseClient *client )
{
	CSteamID steamID;

	if ( SteamGameServer() )
	{
		steamID = SteamGameServer()->CreateUnauthenticatedUserConnection();
	}
	
	client->SetSteamID( steamID );

	SendUpdatedServerDetails();

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *client - 
//-----------------------------------------------------------------------------
void CSteam3Server::NotifyClientDisconnect( CBaseClient *client )
{
	if ( !client || !BIsActive() || !client->IsConnected() || !client->m_SteamID.IsValid() )
		return;

	// Check if the client has a local (anonymous) steam account.  This is the
	// case for bots.  Currently it's also the case for people who connect
	// directly to the SourceTV port.
	if ( client->m_SteamID.GetEAccountType() == k_EAccountTypeAnonGameServer )
	{
		SteamGameServer()->SendUserDisconnect( client->m_SteamID );

		// Clear the steam ID, as it was a dummy one that should not be used again
		client->m_SteamID = CSteamID();
	}
	else
	{

		// All bots should have an anonymous account ID
		Assert( !client->IsFakeClient() );

		USERID_t id = client->GetNetworkID();
		if ( id.idtype != IDTYPE_STEAM )
			return;
	
		// Msg("S3: Sending client disconnect for %x\n", steamIDClient.ConvertToUint64( ) );
		SteamGameServer()->EndAuthSession( client->m_SteamID );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CSteam3Server::NotifyOfLevelChange()
{
	// we're changing levels, so we may not respond for a while
	if ( m_bHasActivePlayers )
	{
		m_bHasActivePlayers = false;
		SendUpdatedServerDetails();
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CSteam3Server::NotifyOfServerNameChange()
{
	SendUpdatedServerDetails();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CSteam3Server::RunFrame()
{
	bool bHasPlayers = ( sv.GetNumClients() > 0 );

	if ( m_bHasActivePlayers != bHasPlayers )
	{
		m_bHasActivePlayers = bHasPlayers;
		SendUpdatedServerDetails();
	}

	static double s_fLastRunCallback = 0.0f;
	double fCurtime = Plat_FloatTime();
	if ( fCurtime - s_fLastRunCallback > 0.1f )
	{
		s_fLastRunCallback = fCurtime;
		SteamGameServer_RunCallbacks();
	}
}


//-----------------------------------------------------------------------------
// Purpose: lets the steam3 servers know our full details
// Input  : bChangingLevels - true if we're going to heartbeat slowly for a while
//-----------------------------------------------------------------------------
void CSteam3Server::SendUpdatedServerDetails()
{
	if ( !BIsActive() || SteamGameServer() == NULL )
		return;

	// Fetch counts that include the dummy slots for SourceTV and reply.
	int nNumClients = sv.GetNumClients();
	int nMaxClients = sv.GetMaxClients();
	int nFakeClients = sv.GetNumFakeClients();

	// Now remove any dummy slots reserved for the Source TV or replay
	// listeners.  The fact that these are "players" should be a Source-specific
	// implementation artifact, and this kludge --- I mean ELEGANT SOLUTION ---
	// should not be propagated to the Steam layer.  Steam should be able to report
	// exactly what we give it to the master server, etc.
	for ( int i = 0 ; i < sv.GetClientCount() ; ++i )
	{
		CBaseClient *cl = (CBaseClient *)sv.GetClient( i );
		if ( !cl->IsConnected() )
			continue;

		bool bHideClient = false;
		if ( cl->IsReplay() || cl->IsHLTV() )
		{
			Assert( cl->IsFakeClient() );
			bHideClient = true;
		}

		if ( cl->IsFakeClient() && !cl->ShouldReportThisFakeClient() )
		{
			bHideClient = true;
		}

		if ( bHideClient )
		{
			--nNumClients;
			--nMaxClients;
			--nFakeClients;

			// And make sure we don't have any local player authentication
			// records within steam for this guy.
			if ( cl->m_SteamID.IsValid() )
			{
				Assert( cl->m_SteamID.BAnonGameServerAccount() );
				SteamGameServer()->SendUserDisconnect( cl->m_SteamID );
				cl->m_SteamID = CSteamID();
			}
		}
	}

	// Apply convar to force reported max player count LAST
	if ( sv_visiblemaxplayers.GetInt() > 0 && sv_visiblemaxplayers.GetInt() < nMaxClients )
		nMaxClients = sv_visiblemaxplayers.GetInt();

	SteamGameServer()->SetMaxPlayerCount( nMaxClients );
	SteamGameServer()->SetBotPlayerCount( nFakeClients );
	SteamGameServer()->SetPasswordProtected( sv.GetPassword() != NULL );
	SteamGameServer()->SetRegion( sv_region.GetString() );
	SteamGameServer()->SetServerName( sv.GetName() );
	if ( hltv && hltv->IsTVRelay() )
	{
		// If we're a relay we can't use the local server data for these
		SteamGameServer()->SetMapName( hltv->GetMapName() );
		SteamGameServer()->SetMaxPlayerCount( hltv->GetMaxClients() );
		SteamGameServer()->SetBotPlayerCount( 0 );
	}
	else
	{
		const char *pszMap = NULL;
		if ( g_iServerGameDLLVersion >= 9 )
			pszMap = serverGameDLL->GetServerBrowserMapOverride();
		if ( pszMap == NULL || *pszMap == '\0' )
			pszMap = sv.GetMapName();
		SteamGameServer()->SetMapName( pszMap );
	}
	if ( hltv && hltv->IsActive() )
	{
		// This is also the case when we're a relay, in which case we never set a game port, so we'll only have a spectator port
		SteamGameServer()->SetSpectatorPort( NET_GetUDPPort( NS_HLTV ) );
		SteamGameServer()->SetSpectatorServerName( hltv->GetName() );
	}
	else
	{
		SteamGameServer()->SetSpectatorPort( 0 );
	}

	UpdateGroupSteamID( false );

	// Form the game data to send

	CUtlString sGameData;

	// Start with whatever the game has
	if ( g_iServerGameDLLVersion >= 9 )
		sGameData = serverGameDLL->GetServerBrowserGameData();

	// Add the value of our steam blocking flag
	char rgchTag[32];
	V_sprintf_safe( rgchTag, "steamblocking:%d", sv_steamblockingcheck.GetInt() );
	if ( !sGameData.IsEmpty() )
	{
		sGameData.Append( "," );
	}
	sGameData.Append( rgchTag );

	SteamGameServer()->SetGameData( sGameData );

//	Msg( "CSteam3Server::SendUpdatedServerDetails: nNumClients=%d, nMaxClients=%d, nFakeClients=%d:\n", nNumClients, nMaxClients, nFakeClients );
//	for ( int i = 0 ; i < sv.GetClientCount() ; ++i )
//	{
//		IClient	*c = sv.GetClient( i );
//		Msg("  %d: %s, connected=%d, replay=%d, fake=%d\n", i, c->GetClientName(), c->IsConnected() ? 1 : 0, c->IsReplay() ? 1 : 0, c->IsFakeClient() ? 1 : 0 );
//	}
}

bool CSteam3Server::IsMasterServerUpdaterSharingGameSocket()
{
	return m_bMasterServerUpdaterSharingGameSocket;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void Heartbeat_f()
{

	if( Steam3Server().SteamGameServer() )
	{
		Steam3Server().SteamGameServer()->ForceHeartbeat();
	}
}

//static ConCommand heartbeat( "heartbeat", Heartbeat_f, "Force heartbeat of master servers", 0 );


//-----------------------------------------------------------------------------
// Purpose: Select Steam gameserver account to login to
//-----------------------------------------------------------------------------
void sv_setsteamaccount_f( const CCommand &args )
{
	if ( Steam3Server().SteamGameServer() && Steam3Server().SteamGameServer()->BLoggedOn() )
	{
		Warning( "Warning: Game server already logged into steam.  You need to use the sv_setsteamaccount command earlier.\n");
		return;
	}

	if ( sv_lan.GetBool() )
	{
		Warning( "Warning: sv_setsteamaccount is not applicable in LAN mode.\n");
	}

	if ( args.ArgC() != 2 )
	{
		Warning( "Usage: sv_setsteamaccount <login_token>\n");
		return;
	}

	Steam3Server().SetAccount( args[1] );
}

static ConCommand sv_setsteamaccount( "sv_setsteamaccount", sv_setsteamaccount_f, "token\nSet game server account token to use for logging in to a persistent game server account", 0 );


static void sv_setsteamgroup_f( IConVar *pConVar, const char *pOldString, float flOldValue );

ConVar			sv_steamgroup( "sv_steamgroup", "", FCVAR_NOTIFY, "The ID of the steam group that this server belongs to. You can find your group's ID on the admin profile page in the steam community.", sv_setsteamgroup_f );


void CSteam3Server::UpdateGroupSteamID( bool bForce )
{
	if ( sv_steamgroup.GetInt() == 0 && !bForce )
		return;
	uint unAccountID = Q_atoi( sv_steamgroup.GetString() );
	m_SteamIDGroupForBlocking.Set( unAccountID, m_SteamIDGS.GetEUniverse(), k_EAccountTypeClan );
	if ( SteamGameServer() )
		SteamGameServer()->AssociateWithClan( m_SteamIDGroupForBlocking );
}

static void sv_setsteamgroup_f( IConVar *pConVar, const char *pOldString, float flOldValue )
{
	if ( sv_lan.GetBool() )
	{
		Warning( "Warning: sv_steamgroup is not applicable in LAN mode.\n");
	}
	Steam3Server().UpdateGroupSteamID( true );
}

static void sv_setsteamblockingcheck_f( IConVar *pConVar, const char *pOldString, float flOldValue )
{
	if ( sv_lan.GetBool() )
	{
		Warning( "Warning: sv_steamblockingcheck is not applicable in LAN mode.\n");
	}
}
