It took me quite some time to figure this one out so I will share what I know in here.
When searching for a dedicated server, getting player count of 0 is expected behavior for OnlineSubsystemSteam since the it never go through the Steam User Authentication process when joining a dedicated server.
Player count for a Presence Session search will always works since SteamMatchmaking()->JoinLobby(LobbyId) gets called upon player join a Presence Session to let steam backend knows that player is in that session. the corresponding step is missing while player join a Dedicated Session.
It would make more sense to do the authentication in the stage of “JoinSession” like what a Presence Session do, But unlike join a steam lobby, authentication on dedicated server require sending data from client to server to complete, Which is not easy to do since a stateful connection between client and server haven’t been established yet and Session-less connection messages are not easily accessible.
It looks strange to me that OnlineSubsystemSteam didn’t handle this process since it is required for some useful server matchmaking functionalities provided by steam to be functional (i.e. backend side query filters like “noplayers”, “notfull” and VAC).
The work around I use is doing authentication in PlayerController after connection have been established and I can use RPC calls to send data needed for authentication.
In MyPlayerController.h define a struct for authentication token:
//A struct holds necessary infos sent from client to complete authentication
USTRUCT()
struct FSteamAuthenticationToken
{
GENERATED_BODY()
public:
bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
//Ar.ByteOrderSerialize(rgchToken, 1024);
Ar << rgchTokenString;
Ar << unTokenLen;
Ar << steamid;
bOutSuccess = true;
return true;
}
public:
FString rgchTokenString;
uint32 unTokenLen = 0;
uint64 steamid;
};
template<>
struct TStructOpsTypeTraits<FSteamAuthenticationToken> : public TStructOpsTypeTraitsBase2<FSteamAuthenticationToken>
{
enum
{
WithNetSerializer = true
};
};
Define RPC functions, our AuthTicket and Steamcallback:
UFUNCTION(Client, Reliable)
void ClientRequestSteamAuth();
UFUNCTION(Server, Reliable, WithValidation)
void ServerBeginSteamAuth(FSteamAuthenticationToken AuthToken);
//A AuthTickect client should keep track of, used when client log off
HAuthTicket m_hAuthTicket;
//Get called on server when authentication complete with result.
STEAM_GAMESERVER_CALLBACK_MANUAL(ANetworkPlayerController, OnAuthTicketResponse, ValidateAuthTicketResponse_t, OnAuthTicketResponseCallback);
MyPlayerController.cpp:
void ANetworkPlayerController::ClientRequestSteamAuth_Implementation()
{
if (SteamUser())
{
uint8 rgchToken[1024];
uint32 unTokenLen = 0;
m_hAuthTicket = SteamUser()->GetAuthSessionTicket(rgchToken, sizeof(rgchToken), &unTokenLen);
FSteamAuthenticationToken SteamAuthToken;
SteamAuthToken.rgchTokenString = BytesToString(rgchToken, 1024);
SteamAuthToken.unTokenLen = unTokenLen;
SteamAuthToken.steamid = SteamUser()->GetSteamID().ConvertToUint64();
if (m_hAuthTicket != k_HAuthTicketInvalid)
{
ServerBeginSteamAuth(SteamAuthToken);
}
else
{
//TODO: Client disconnect self from server since authentication failed.
}
}
}
bool ANetworkPlayerController::ServerBeginSteamAuth_Validate(FSteamAuthenticationToken AuthToken)
{
return true;
}
void ANetworkPlayerController::ServerBeginSteamAuth_Implementation(FSteamAuthenticationToken AuthToken)
{
uint8 rgchToken[1024];
StringToBytes(AuthToken.rgchTokenString, rgchToken, 1024);
OnAuthTicketResponseCallback.Register(this, &ANetworkPlayerController::OnAuthTicketResponse);
if (k_EBeginAuthSessionResultOK != SteamGameServer()->BeginAuthSession(rgchToken, AuthToken.unTokenLen, AuthToken.steamid))
{
//TODO: Server disconnect client since authentication failed.
SteamGameServer()->EndAuthSession(AuthToken.steamid);
}
}
void ANetworkPlayerController::OnAuthTicketResponse(ValidateAuthTicketResponse_t* CallbackData)
{
uint64 PlayerSteamId = *((uint64*)PlayerState->UniqueId.GetUniqueNetId()->GetBytes());
if (CallbackData->m_SteamID == PlayerSteamId)
{
OnAuthTicketResponseCallback.Unregister();
if (CallbackData->m_eAuthSessionResponse > EAuthSessionResponse::k_EAuthSessionResponseOK)
{
//TODO: Server disconnect client since authentication failed.
}
}
}
Also notice SteamUser()->CancelAuthTicket(m_hAuthTicket);
should be called upon client actively logoff or lost connection to server, which I did not include in code above.