diff options
Diffstat (limited to 'engine/sv_steamauth.cpp')
| -rw-r--r-- | engine/sv_steamauth.cpp | 1069 |
1 files changed, 1069 insertions, 0 deletions
diff --git a/engine/sv_steamauth.cpp b/engine/sv_steamauth.cpp new file mode 100644 index 0000000..144ae77 --- /dev/null +++ b/engine/sv_steamauth.cpp @@ -0,0 +1,1069 @@ +//========= 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( true ); + 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"); + } +} |