1367 lines
47 KiB
C++
1367 lines
47 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "OnlineSessionAsyncServerSteam.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Online/OnlineSessionNames.h"
|
|
#include "IPAddressSteam.h"
|
|
#include "SteamSessionKeys.h"
|
|
#include "SteamUtilities.h"
|
|
#include "OnlineAuthInterfaceSteam.h"
|
|
#include <steam/steamuniverse.h>
|
|
|
|
#if WITH_ENGINE
|
|
#include "GameFramework/GameStateBase.h"
|
|
#include "GameFramework/PlayerState.h"
|
|
#include "OnlineSubsystemUtils.h"
|
|
#include "SocketSubsystem.h"
|
|
#endif //WITH_ENGINE
|
|
|
|
|
|
/** Turn on Steam filter generation output */
|
|
#define DEBUG_STEAM_FILTERS 1
|
|
|
|
/* Server values needed to advertise with Steam (NOTE: Steam expects UTF8)
|
|
*
|
|
* Specify these in your Target.cs files for your project.
|
|
* Make sure to escape the strings!
|
|
* If these do not match the same values on the Steam partner backend,
|
|
* matchmaking will not work for your dedicated server
|
|
*/
|
|
#ifndef UE_PROJECT_STEAMPRODUCTNAME
|
|
#define UE_PROJECT_STEAMPRODUCTNAME "unrealdk"
|
|
#endif //UE_PROJECT_STEAMPRODUCTNAME
|
|
|
|
#ifndef UE_PROJECT_STEAMGAMEDIR
|
|
#define UE_PROJECT_STEAMGAMEDIR "unrealtest"
|
|
#endif // UE_PROJECT_STEAMGAMEDIR
|
|
|
|
#ifndef UE_PROJECT_STEAMGAMEDESC
|
|
#define UE_PROJECT_STEAMGAMEDESC "Unreal Test!"
|
|
#endif // UE_PROJECT_STEAMGAMEDESC
|
|
|
|
/**
|
|
* Get the engine unique build id as Steam key
|
|
*
|
|
* @return BuildUniqueId as an FString
|
|
*/
|
|
FString GetBuildIdAsSteamKey(const FOnlineSessionSettings& SessionSettings)
|
|
{
|
|
return FString::Printf(TEXT("%s:%d"), UTF8_TO_TCHAR(STEAMKEY_BUILDUNIQUEID), SessionSettings.BuildUniqueId);
|
|
}
|
|
|
|
/**
|
|
* Get the session flags bitfield as an FString
|
|
*
|
|
* @param SessionSettings settings to transform into string
|
|
*
|
|
* @return SessionFlags as an FString
|
|
*/
|
|
FString GetSessionFlagsAsString(const FOnlineSessionSettings& SessionSettings)
|
|
{
|
|
int32 BitShift = 0;
|
|
int32 SessionFlags = 0;
|
|
// Some of this is redundant but included for completeness (bAntiCheatProtected, etc)
|
|
SessionFlags |= (SessionSettings.bShouldAdvertise ? 1 : 0) << BitShift++;
|
|
SessionFlags |= (SessionSettings.bAllowJoinInProgress ? 1 : 0) << BitShift++;
|
|
SessionFlags |= (SessionSettings.bIsLANMatch ? 1 : 0) << BitShift++;
|
|
SessionFlags |= (SessionSettings.bIsDedicated ? 1 : 0) << BitShift++;
|
|
SessionFlags |= (SessionSettings.bUsesStats ? 1 : 0) << BitShift++;
|
|
SessionFlags |= (SessionSettings.bAllowInvites ? 1 : 0) << BitShift++;
|
|
SessionFlags |= (SessionSettings.bUsesPresence ? 1 : 0) << BitShift++;
|
|
SessionFlags |= (SessionSettings.bAllowJoinViaPresence ? 1 : 0) << BitShift++;
|
|
SessionFlags |= (SessionSettings.bAllowJoinViaPresenceFriendsOnly ? 1 : 0) << BitShift++;
|
|
SessionFlags |= (SessionSettings.bAntiCheatProtected ? 1 : 0) << BitShift++;
|
|
|
|
return FString::FromInt(SessionFlags);
|
|
}
|
|
|
|
/**
|
|
* Get all relevant FOnlineSessionSettings data as a series of Key,Value pairs
|
|
*
|
|
* @param SessionSettings session settings to get key, value pairs from
|
|
* @param KeyValuePairs key value pair structure to add to
|
|
*/
|
|
void GetServerKeyValuePairsFromSessionSettings(const FOnlineSessionSettings& SessionSettings, FSteamSessionKeyValuePairs& KeyValuePairs, EOnlineDataAdvertisementType::Type AdvertisementType)
|
|
{
|
|
//KeyValuePairs.Add(STEAMKEY_NUMPUBLICCONNECTIONS, FString::FromInt(SessionSettings.NumPublicConnections));
|
|
//KeyValuePairs.Add(STEAMKEY_NUMPRIVATECONNECTIONS, FString::FromInt(SessionSettings.NumPrivateConnections));
|
|
|
|
FString KeyStr;
|
|
for (FSessionSettings::TConstIterator It(SessionSettings.Settings); It; ++It)
|
|
{
|
|
FName Key = It.Key();
|
|
const FOnlineSessionSetting& Setting = It.Value();
|
|
if (Setting.AdvertisementType == AdvertisementType)
|
|
{
|
|
if (SessionKeyToSteamKey(Key, Setting.Data, KeyStr))
|
|
{
|
|
FString SettingStr = Setting.Data.ToString();
|
|
if (!SettingStr.IsEmpty())
|
|
{
|
|
KeyValuePairs.Add(KeyStr, SettingStr);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Empty session setting %s %s of type %s"), *Key.ToString(), *Setting.ToString(), EOnlineKeyValuePairDataType::ToString(Setting.Data.GetType()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Unsupported session setting %s %s of type %s"), *Key.ToString(), *Setting.ToString(), EOnlineKeyValuePairDataType::ToString(Setting.Data.GetType()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all relevant FSessionInfoSteam data as a series of Key,Value pairs
|
|
*
|
|
* @param SessionInfo session info to get key, value pairs from
|
|
* @param KeyValuePairs key value pair structure to add to
|
|
*/
|
|
void GetServerKeyValuePairsFromSessionInfo(FOnlineSessionInfoSteam* SessionInfo, FSteamSessionKeyValuePairs& KeyValuePairs)
|
|
{
|
|
FOnlineSubsystemSteam* SteamSubsystem = static_cast<FOnlineSubsystemSteam*>(IOnlineSubsystem::Get(STEAM_SUBSYSTEM));
|
|
FSteamConnectionMethod MethodUsed = SessionInfo->ConnectionMethod;
|
|
|
|
// We should not enter this check here if we're using LAN.
|
|
if (SteamSubsystem && GConfig && MethodUsed == FSteamConnectionMethod::None)
|
|
{
|
|
bool bUseRelays = false;
|
|
bool bIsDefaultSubsystem = false;
|
|
GConfig->GetBool(TEXT("OnlineSubsystemSteam"), TEXT("bAllowP2PPacketRelay"), bUseRelays, GEngineIni);
|
|
// If this config specifically does not exist, then set it to true
|
|
if (!GConfig->GetBool(TEXT("OnlineSubsystemSteam"), TEXT("bUseSteamNetworking"), bIsDefaultSubsystem, GEngineIni))
|
|
{
|
|
bIsDefaultSubsystem = true;
|
|
}
|
|
|
|
// Using Steam sockets. This checks if the SteamSockets plugin is enabled
|
|
if (!SteamSubsystem->IsUsingSteamNetworking())
|
|
{
|
|
if (!bUseRelays)
|
|
{
|
|
MethodUsed = FSteamConnectionMethod::Direct;
|
|
}
|
|
else
|
|
{
|
|
MethodUsed = FSteamConnectionMethod::P2P;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Determine if we are a default. If so, we're using SteamNetworking P2P.
|
|
if (bIsDefaultSubsystem)
|
|
{
|
|
MethodUsed = FSteamConnectionMethod::P2P;
|
|
}
|
|
else
|
|
{
|
|
MethodUsed = FSteamConnectionMethod::Direct;
|
|
}
|
|
}
|
|
|
|
// Set this if it hasn't been set yet.
|
|
SessionInfo->ConnectionMethod = MethodUsed;
|
|
}
|
|
|
|
KeyValuePairs.Add(STEAMKEY_CONNECTIONMETHOD, FString::Printf(TEXT("%s"), *LexToString(MethodUsed)));
|
|
|
|
if (SessionInfo->SteamP2PAddr.IsValid())
|
|
{
|
|
KeyValuePairs.Add(STEAMKEY_P2PADDR, SessionInfo->SteamP2PAddr->ToString(false));
|
|
KeyValuePairs.Add(STEAMKEY_P2PPORT, FString::FromInt(SessionInfo->SteamP2PAddr->GetPort()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all relevant FSession data as a series of Key,Value pairs
|
|
*
|
|
* @param Session session data to get key, value pairs from
|
|
* @param KeyValuePairs key value pair structure to add to
|
|
*/
|
|
void GetServerKeyValuePairsFromSession(const FOnlineSession* Session, FSteamSessionKeyValuePairs& KeyValuePairs)
|
|
{
|
|
FUniqueNetIdSteam* SteamId = (FUniqueNetIdSteam*)(Session->OwningUserId.Get());
|
|
FString OwningUserIdStr = SteamId->ToString();
|
|
KeyValuePairs.Add(STEAMKEY_OWNINGUSERID, OwningUserIdStr);
|
|
KeyValuePairs.Add(STEAMKEY_OWNINGUSERNAME, Session->OwningUserName);
|
|
//KeyValuePairs.Add(STEAMKEY_NUMOPENPRIVATECONNECTIONS, FString::FromInt(Session->NumOpenPrivateConnections));
|
|
//KeyValuePairs.Add(STEAMKEY_NUMOPENPUBLICCONNECTIONS, FString::FromInt(Session->NumOpenPublicConnections));
|
|
}
|
|
|
|
/**
|
|
* Update the backend with the currently defined settings
|
|
*
|
|
* @param World current running world instance
|
|
* @param SessionName name of session to update published settings
|
|
*/
|
|
void UpdatePublishedSettings(UWorld* World, FNamedOnlineSession* Session)
|
|
{
|
|
ISteamGameServer* SteamGameServerPtr = SteamGameServer();
|
|
check(SteamGameServerPtr);
|
|
|
|
// Copy the current settings so we can remove the ones used for well defined search parameters
|
|
FOnlineSessionSettings TempSessionSettings = Session->SessionSettings;
|
|
|
|
// Server name
|
|
FString ServerName = Session->OwningUserName;
|
|
SteamGameServerPtr->SetServerName(TCHAR_TO_UTF8(*ServerName));
|
|
|
|
// Max user slots reported
|
|
int32 NumTotalSlots = Session->SessionSettings.NumPublicConnections + Session->SessionSettings.NumPrivateConnections;
|
|
SteamGameServerPtr->SetMaxPlayerCount(NumTotalSlots);
|
|
|
|
// Region setting
|
|
FString Region(TEXT(""));
|
|
SteamGameServerPtr->SetRegion(TCHAR_TO_UTF8(*Region));
|
|
|
|
// @TODO ONLINE Password protected or not
|
|
SteamGameServerPtr->SetPasswordProtected(false);
|
|
|
|
// Map name
|
|
FString MapName;
|
|
if (TempSessionSettings.Get(SETTING_MAPNAME, MapName) && !MapName.IsEmpty())
|
|
{
|
|
SteamGameServerPtr->SetMapName(TCHAR_TO_UTF8(*MapName));
|
|
}
|
|
TempSessionSettings.Remove(SETTING_MAPNAME);
|
|
|
|
// Bot Count
|
|
int32 BotCount = 0;
|
|
if (TempSessionSettings.Get(SETTING_NUMBOTS, BotCount))
|
|
{
|
|
SteamGameServerPtr->SetBotPlayerCount(BotCount);
|
|
}
|
|
TempSessionSettings.Remove(SETTING_NUMBOTS);
|
|
|
|
// Update all the players names/scores
|
|
#if WITH_ENGINE
|
|
if (World)
|
|
{
|
|
AGameStateBase const* const GameState = World->GetGameState();
|
|
if (GameState)
|
|
{
|
|
for (int32 PlayerIdx=0; PlayerIdx < GameState->PlayerArray.Num(); PlayerIdx++)
|
|
{
|
|
APlayerState const* const PlayerState = GameState->PlayerArray[PlayerIdx];
|
|
if (PlayerState && PlayerState->GetUniqueId().IsValid())
|
|
{
|
|
CSteamID SteamId(*(uint64*)PlayerState->GetUniqueId()->GetBytes());
|
|
SteamGameServerPtr->BUpdateUserData(SteamId, TCHAR_TO_UTF8(*PlayerState->GetPlayerName()), PlayerState->GetScore());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif //WITH_ENGINE
|
|
|
|
// Get the advertised session settings out as Steam key/value pairs
|
|
FSteamSessionKeyValuePairs ViaOnlineServicePairs;
|
|
GetServerKeyValuePairsFromSession(Session, ViaOnlineServicePairs);
|
|
|
|
// Grab the session flags
|
|
ViaOnlineServicePairs.Add(STEAMKEY_SESSIONFLAGS, GetSessionFlagsAsString(Session->SessionSettings));
|
|
|
|
// Start the game tags with the build id so search results can early out
|
|
FString GameTagsString = GetBuildIdAsSteamKey(Session->SessionSettings);
|
|
|
|
GetServerKeyValuePairsFromSessionSettings(TempSessionSettings, ViaOnlineServicePairs, EOnlineDataAdvertisementType::ViaOnlineService);
|
|
|
|
FSteamSessionKeyValuePairs ViaPingPairs;
|
|
GetServerKeyValuePairsFromSessionSettings(TempSessionSettings, ViaPingPairs, EOnlineDataAdvertisementType::ViaPingOnly);
|
|
|
|
FSteamSessionKeyValuePairs ViaOnlineServicePingPairs;
|
|
GetServerKeyValuePairsFromSessionSettings(TempSessionSettings, ViaOnlineServicePingPairs, EOnlineDataAdvertisementType::ViaOnlineServiceAndPing);
|
|
|
|
// Generate the tag data first, due to its small size
|
|
for (FSteamSessionKeyValuePairs::TConstIterator It(ViaOnlineServicePairs); It; ++It)
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Verbose, TEXT("Server List Game Tags (%s, %s)"), *It.Key(), *It.Value());
|
|
FString NewKey = FString::Printf(TEXT(",%s:%s"), *It.Key(), *It.Value());
|
|
if (GameTagsString.Len() + NewKey.Len() < k_cbMaxGameServerTags)
|
|
{
|
|
GameTagsString += NewKey;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Server setting %s overflows Steam SetGameTags call"), *NewKey);
|
|
}
|
|
}
|
|
|
|
// Push in the other tags from session info (these are dropped from the tag queries for space reasons)
|
|
if (Session->SessionInfo.IsValid())
|
|
{
|
|
FOnlineSessionInfoSteam* SessionInfo = (FOnlineSessionInfoSteam*)(Session->SessionInfo.Get());
|
|
GetServerKeyValuePairsFromSessionInfo(SessionInfo, ViaOnlineServicePairs);
|
|
}
|
|
|
|
// Append both advertising sets with the dual advertisement filters
|
|
ViaOnlineServicePairs.Append(ViaOnlineServicePingPairs);
|
|
|
|
// Create the properly formatted Steam string (ie key:value,key:value,key) for GameData
|
|
FString GameDataString;
|
|
for (FSteamSessionKeyValuePairs::TConstIterator It(ViaOnlineServicePairs); It; ++It)
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Verbose, TEXT("Server List Game Data (%s, %s)"), *It.Key(), *It.Value());
|
|
FString NewKey = FString::Printf(TEXT("%s:%s"), *It.Key(), *It.Value());
|
|
|
|
if (GameDataString.Len() + NewKey.Len() < k_cbMaxGameServerGameData)
|
|
{
|
|
GameDataString += ((GameDataString.Len() > 0) ? "," : "") + NewKey;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Server setting %s overflows Steam SetGameData call"), *NewKey);
|
|
}
|
|
}
|
|
|
|
// Small and searchable game tags (returned in initial server query structure)
|
|
if (GameTagsString.Len() > 0 && GameTagsString.Len() < k_cbMaxGameServerTags)
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Verbose, TEXT("SetGameTags(%s)"), *GameTagsString);
|
|
SteamGameServerPtr->SetGameTags(TCHAR_TO_UTF8(*GameTagsString));
|
|
}
|
|
|
|
// Large and searchable game data (never returned, used to filter things on the server list)
|
|
if (GameDataString.Len() > 0 && GameDataString.Len() < k_cbMaxGameServerGameData)
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Verbose, TEXT("SetGameData(%s)"), *GameDataString);
|
|
SteamGameServerPtr->SetGameData(TCHAR_TO_UTF8(*GameDataString));
|
|
}
|
|
|
|
// Set the advertised filter keys (these can not be filtered at server list level, only client side via talking to the server)
|
|
SteamGameServerPtr->ClearAllKeyValues();
|
|
|
|
// Push all the filters in for the KeyValue data.
|
|
ViaOnlineServicePairs.Append(ViaPingPairs);
|
|
|
|
// Key value pairs sent as rules (requires secondary RulesRequest call)
|
|
for (FSteamSessionKeyValuePairs::TConstIterator AuxKeyIt(ViaOnlineServicePairs); AuxKeyIt; ++AuxKeyIt)
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Verbose, TEXT("Pushing Server KVData (%s, %s)"), *AuxKeyIt.Key(), *AuxKeyIt.Value());
|
|
SteamGameServerPtr->SetKeyValue(TCHAR_TO_UTF8(*AuxKeyIt.Key()), TCHAR_TO_UTF8(*AuxKeyIt.Value()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a human readable description of task
|
|
*/
|
|
FString FOnlineAsyncTaskSteamCreateServer::ToString() const
|
|
{
|
|
return FString::Printf(TEXT("FOnlineAsyncTaskSteamCreateServer bWasSuccessful: %d"), WasSuccessful());
|
|
}
|
|
|
|
/**
|
|
* Give the async task time to do its work
|
|
* Can only be called on the async task manager thread
|
|
*/
|
|
void FOnlineAsyncTaskSteamCreateServer::Tick()
|
|
{
|
|
FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr<FOnlineSessionSteam>(Subsystem->GetSessionInterface());
|
|
if (!bInit)
|
|
{
|
|
ISteamGameServer* SteamGameServerPtr = SteamGameServer();
|
|
FNamedOnlineSession* Session = (SessionInt.IsValid()) ? SessionInt->GetNamedSession(SessionName) : nullptr;
|
|
if (Session != nullptr && SteamGameServerPtr != nullptr)
|
|
{
|
|
bool bWantsDedicated = Session->SessionSettings.bIsDedicated;
|
|
UE_LOG_ONLINE_SESSION(Verbose, TEXT("Starting Steam game server. Dedicated? %d Game Dir is: %s Product Name is: %s\nGame Desc is: %s"),
|
|
bWantsDedicated,
|
|
ANSI_TO_TCHAR((UE_PROJECT_STEAMGAMEDIR)),
|
|
ANSI_TO_TCHAR((UE_PROJECT_STEAMPRODUCTNAME)),
|
|
ANSI_TO_TCHAR((UE_PROJECT_STEAMGAMEDESC)));
|
|
|
|
SteamGameServerPtr->SetModDir(UE_PROJECT_STEAMGAMEDIR);
|
|
SteamGameServerPtr->SetProduct(UE_PROJECT_STEAMPRODUCTNAME);
|
|
SteamGameServerPtr->SetGameDescription(UE_PROJECT_STEAMGAMEDESC);
|
|
SteamGameServerPtr->SetDedicatedServer(bWantsDedicated);
|
|
|
|
if (!SteamGameServerPtr->BLoggedOn())
|
|
{
|
|
// Login the server with Steam
|
|
SteamGameServerPtr->LogOnAnonymous();
|
|
}
|
|
|
|
// Setup advertisement and force the initial update
|
|
//SteamGameServerPtr->SetHeartbeatInterval(-1); // deprecated in 1.53
|
|
SteamGameServerPtr->SetAdvertiseServerActive(true); // renamed EnableHeartbeats to SetAdvertiseServerActive in 1.53
|
|
// SteamGameServerPtr->ForceHeartbeat(); // deprecated in 1.53
|
|
|
|
bInit = true;
|
|
}
|
|
}
|
|
|
|
// Wait for the connection and policy response callbacks
|
|
if (bInit && SessionInt->bSteamworksGameServerConnected && SessionInt->GameServerSteamId->IsValid() && SessionInt->bPolicyResponseReceived)
|
|
{
|
|
bIsComplete = true;
|
|
bWasSuccessful = true;
|
|
}
|
|
else
|
|
{
|
|
float AsyncTimeout = ASYNC_TASK_TIMEOUT;
|
|
GConfig->GetFloat(TEXT("OnlineSubsystemSteam"), TEXT("OnlineAsyncTaskSteamCreateServerTimeout"), AsyncTimeout, GEngineIni);
|
|
|
|
// Fallback timeout in case we don't hear from Steam
|
|
if (GetElapsedTime() >= AsyncTimeout)
|
|
{
|
|
bIsComplete = true;
|
|
bWasSuccessful = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Give the async task a chance to marshal its data back to the game thread
|
|
* Can only be called on the game thread by the async task manager
|
|
*/
|
|
void FOnlineAsyncTaskSteamCreateServer::Finalize()
|
|
{
|
|
FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr<FOnlineSessionSteam>(Subsystem->GetSessionInterface());
|
|
if (bWasSuccessful)
|
|
{
|
|
FNamedOnlineSession* Session = SessionInt->GetNamedSession(SessionName);
|
|
if (Session)
|
|
{
|
|
// Setup the host session info
|
|
FOnlineSessionInfoSteam* NewSessionInfo = new FOnlineSessionInfoSteam(ESteamSession::AdvertisedSessionHost, *SessionInt->GameServerSteamId);
|
|
NewSessionInfo->Init();
|
|
|
|
ISteamGameServer* SteamGameServerPtr = SteamGameServer();
|
|
check(SteamGameServerPtr);
|
|
|
|
#if WITH_ENGINE
|
|
// Create the proper Steam P2P address for this machine
|
|
NewSessionInfo->SteamP2PAddr = ISocketSubsystem::Get()->GetLocalBindAddr(*GLog);
|
|
NewSessionInfo->SteamP2PAddr->SetPort(Subsystem->GetGameServerGamePort());
|
|
UE_LOG_ONLINE_SESSION(Verbose, TEXT("Server SteamP2P IP: %s"), *NewSessionInfo->SteamP2PAddr->ToString(true));
|
|
|
|
// Create the proper ip address for this server
|
|
NewSessionInfo->HostAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
|
|
NewSessionInfo->HostAddr->SetIp(SteamGameServerPtr->GetPublicIP().m_unIPv4);
|
|
NewSessionInfo->HostAddr->SetPort(Subsystem->GetGameServerGamePort());
|
|
UE_LOG_ONLINE_SESSION(Verbose, TEXT("Server IP: %s"), *NewSessionInfo->HostAddr->ToString(true));
|
|
#endif //WITH_ENGINE
|
|
|
|
if (!Session->OwningUserId.IsValid())
|
|
{
|
|
check(Session->SessionSettings.bIsDedicated);
|
|
// Associate the dedicated server anonymous login as the owning user
|
|
Session->OwningUserId = SessionInt->GameServerSteamId;
|
|
|
|
// Figure if we need to override the server name here
|
|
FString CustomDedicatedServerName = SessionInt->GetCustomDedicatedServerName();
|
|
Session->OwningUserName = (!CustomDedicatedServerName.IsEmpty()) ? CustomDedicatedServerName : Session->OwningUserId->ToString();
|
|
}
|
|
|
|
bool bShouldUseAdvertise = true;
|
|
FOnlineAuthSteamPtr SteamAuth = Subsystem->GetAuthInterface();
|
|
if (SteamAuth.IsValid())
|
|
{
|
|
// Do not use the old advertisegame function because SteamAuth will handle it for us
|
|
bShouldUseAdvertise = !SteamAuth->IsSessionAuthEnabled();
|
|
}
|
|
|
|
Session->SessionInfo = MakeShareable(NewSessionInfo);
|
|
Session->SessionSettings.bAntiCheatProtected = SteamGameServerPtr->BSecure() != 0 ? true : false;
|
|
|
|
Session->SessionState = EOnlineSessionState::Pending;
|
|
|
|
#if WITH_ENGINE
|
|
UWorld* World = GetWorldForOnline(Subsystem->GetInstanceName());
|
|
UpdatePublishedSettings(World, Session);
|
|
#endif //WITH_ENGINE
|
|
|
|
SessionInt->RegisterLocalPlayers(Session);
|
|
|
|
if (SteamUser() && bShouldUseAdvertise)
|
|
{
|
|
UE_LOG_ONLINE(Warning, TEXT("AUTH: CreateServerSteam is calling the depricated AdvertiseGame call"));
|
|
SteamUser()->AdvertiseGame(k_steamIDNonSteamGS, SteamGameServerPtr->GetPublicIP().m_unIPv4, Subsystem->GetGameServerGamePort());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("No session %s found to update with Steam backend"), *SessionName.ToString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SessionInt->RemoveNamedSession(SessionName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Async task is given a chance to trigger it's delegates
|
|
*/
|
|
void FOnlineAsyncTaskSteamCreateServer::TriggerDelegates()
|
|
{
|
|
IOnlineSessionPtr SessionInt = Subsystem->GetSessionInterface();
|
|
if (SessionInt.IsValid())
|
|
{
|
|
SessionInt->TriggerOnCreateSessionCompleteDelegates(SessionName, bWasSuccessful);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a human readable description of task
|
|
*/
|
|
FString FOnlineAsyncTaskSteamUpdateServer::ToString() const
|
|
{
|
|
return FString::Printf(TEXT("FOnlineAsyncTaskSteamUpdateServer bWasSuccessful: %d Session: %s"),
|
|
WasSuccessful(),
|
|
*SessionName.ToString());
|
|
}
|
|
|
|
/**
|
|
* Give the async task time to do its work
|
|
* Can only be called on the async task manager thread
|
|
*/
|
|
void FOnlineAsyncTaskSteamUpdateServer::Tick()
|
|
{
|
|
FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr<FOnlineSessionSteam>(Subsystem->GetSessionInterface());
|
|
FNamedOnlineSession* Session = SessionInt->GetNamedSession(SessionName);
|
|
if (Session)
|
|
{
|
|
bool bUsesPresence = Session->SessionSettings.bUsesPresence;
|
|
if (bUsesPresence != NewSessionSettings.bUsesPresence)
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Can't change presence settings on existing session %s, ignoring."), *SessionName.ToString());
|
|
}
|
|
|
|
Session->SessionSettings = NewSessionSettings;
|
|
Session->SessionSettings.bUsesPresence = bUsesPresence;
|
|
|
|
#if WITH_ENGINE
|
|
if (bUpdateOnlineData)
|
|
{
|
|
UWorld* World = GetWorldForOnline(Subsystem->GetInstanceName());
|
|
|
|
// Server list update
|
|
UpdatePublishedSettings(World, Session);
|
|
}
|
|
#endif //WITH_ENGINE
|
|
|
|
bWasSuccessful = true;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("No session %s found to update with Steam backend"), *SessionName.ToString());
|
|
}
|
|
|
|
bIsComplete = true;
|
|
}
|
|
|
|
/**
|
|
* Async task is given a chance to trigger it's delegates
|
|
*/
|
|
void FOnlineAsyncTaskSteamUpdateServer::TriggerDelegates()
|
|
{
|
|
IOnlineSessionPtr SessionInt = Subsystem->GetSessionInterface();
|
|
if (SessionInt.IsValid())
|
|
{
|
|
SessionInt->TriggerOnUpdateSessionCompleteDelegates(SessionName, bWasSuccessful);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a human readable description of task
|
|
*/
|
|
FString FOnlineAsyncTaskSteamLogoffServer::ToString() const
|
|
{
|
|
return FString::Printf(TEXT("FOnlineAsyncTaskSteamLogoffServer bWasSuccessful: %d"), WasSuccessful());
|
|
}
|
|
|
|
/**
|
|
* Give the async task time to do its work
|
|
* Can only be called on the async task manager thread
|
|
*/
|
|
void FOnlineAsyncTaskSteamLogoffServer::Tick()
|
|
{
|
|
if (!bInit)
|
|
{
|
|
// @TODO ONLINE Listen Servers need to unset rich presence
|
|
//SteamFriends()->SetRichPresence("connect", ""); for listed sessions
|
|
SteamGameServer()->SetAdvertiseServerActive(false); // renamed EnableHeartbeats to SetAdvertiseServerActive in 1.53
|
|
SteamGameServer()->LogOff();
|
|
bInit = true;
|
|
}
|
|
|
|
// Wait for the disconnect
|
|
FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr<FOnlineSessionSteam>(Subsystem->GetSessionInterface());
|
|
if (!SessionInt->bSteamworksGameServerConnected && !SessionInt->GameServerSteamId.IsValid())
|
|
{
|
|
bIsComplete = true;
|
|
bWasSuccessful = true;
|
|
}
|
|
else
|
|
{
|
|
// Fallback timeout in case we don't hear from Steam
|
|
float AsyncTimeout = ASYNC_TASK_TIMEOUT;
|
|
GConfig->GetFloat(TEXT("OnlineSubsystemSteam"), TEXT("OnlineAsyncTaskSteamLogoffServerTimeout"), AsyncTimeout, GEngineIni);
|
|
|
|
if (GetElapsedTime() >= AsyncTimeout)
|
|
{
|
|
SessionInt->bSteamworksGameServerConnected = false;
|
|
SessionInt->GameServerSteamId = NULL;
|
|
bIsComplete = true;
|
|
bWasSuccessful = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fills in the proxy search result with all the rules returned by the aux query
|
|
*
|
|
* @return true if session was successfully created, false otherwise
|
|
*/
|
|
bool FPendingSearchResultSteam::FillSessionFromServerRules()
|
|
{
|
|
bool bSuccess = true;
|
|
|
|
// Create the session info
|
|
TSharedPtr<FOnlineSessionInfoSteam> SessionInfo = MakeShareable(new FOnlineSessionInfoSteam(ESteamSession::AdvertisedSessionClient, *ServerId));
|
|
TSharedRef<FInternetAddrSteam> SteamP2PAddr = MakeShareable(new FInternetAddrSteam);
|
|
|
|
FOnlineSession* Session = &PendingSearchResult.Session;
|
|
|
|
// Make sure we hit the important keys
|
|
int32 KeysFound = 0;
|
|
int32 SteamAddrKeysFound = 0;
|
|
|
|
for (FSteamSessionKeyValuePairs::TConstIterator It(ServerRules); It; ++It)
|
|
{
|
|
// if (FCStringAnsi::Stricmp(TCHAR_TO_ANSI(*It.Key()), STEAMKEY_NUMPUBLICCONNECTIONS) == 0)
|
|
// {
|
|
// Session->SessionSettings.NumPublicConnections = FCString::Atoi(*It.Value());
|
|
// KeysFound++;
|
|
// }
|
|
// else if (FCStringAnsi::Stricmp(TCHAR_TO_ANSI(*It.Key()), STEAMKEY_NUMPRIVATECONNECTIONS) == 0)
|
|
// {
|
|
// Session->SessionSettings.NumPrivateConnections = FCString::Atoi(*It.Value());
|
|
// KeysFound++;
|
|
// }
|
|
if (FCStringAnsi::Stricmp(TCHAR_TO_ANSI(*It.Key()), STEAMKEY_SESSIONFLAGS) == 0)
|
|
{
|
|
int32 BitShift = 0;
|
|
int32 SessionFlags = FCString::Atoi(*It.Value());
|
|
Session->SessionSettings.bShouldAdvertise = (SessionFlags & (1 << BitShift++)) ? true : false;
|
|
Session->SessionSettings.bAllowJoinInProgress = (SessionFlags & (1 << BitShift++)) ? true : false;
|
|
Session->SessionSettings.bIsLANMatch = (SessionFlags & (1 << BitShift++)) ? true : false;
|
|
Session->SessionSettings.bIsDedicated = (SessionFlags & (1 << BitShift++)) ? true : false;
|
|
Session->SessionSettings.bUsesStats = (SessionFlags & (1 << BitShift++)) ? true : false;
|
|
Session->SessionSettings.bAllowInvites = (SessionFlags & (1 << BitShift++)) ? true : false;
|
|
Session->SessionSettings.bUsesPresence = (SessionFlags & (1 << BitShift++)) ? true : false;
|
|
Session->SessionSettings.bAllowJoinViaPresence = (SessionFlags & (1 << BitShift++)) ? true : false;
|
|
Session->SessionSettings.bAllowJoinViaPresenceFriendsOnly = (SessionFlags & (1 << BitShift++)) ? true : false;
|
|
Session->SessionSettings.bAntiCheatProtected = (SessionFlags & (1 << BitShift++)) ? true : false;
|
|
KeysFound++;
|
|
}
|
|
else if (FCStringAnsi::Stricmp(TCHAR_TO_ANSI(*It.Key()), STEAMKEY_OWNINGUSERID) == 0)
|
|
{
|
|
uint64 UniqueId = FCString::Atoi64(*It.Value());
|
|
if (UniqueId != 0)
|
|
{
|
|
Session->OwningUserId = FUniqueNetIdSteam::Create(UniqueId);
|
|
KeysFound++;
|
|
}
|
|
}
|
|
else if (FCStringAnsi::Stricmp(TCHAR_TO_ANSI(*It.Key()), STEAMKEY_OWNINGUSERNAME) == 0)
|
|
{
|
|
if (FCString::Strlen(*It.Value()) > 0)
|
|
{
|
|
Session->OwningUserName = It.Value();
|
|
KeysFound++;
|
|
}
|
|
}
|
|
else if (FCStringAnsi::Stricmp(TCHAR_TO_ANSI(*It.Key()), STEAMKEY_CONNECTIONMETHOD) == 0)
|
|
{
|
|
SessionInfo->ConnectionMethod = ToConnectionMethod(It.Value());
|
|
++KeysFound;
|
|
}
|
|
// else if (FCStringAnsi::Stricmp(TCHAR_TO_ANSI(*It.Key()), STEAMKEY_NUMOPENPRIVATECONNECTIONS) == 0)
|
|
// {
|
|
// Session->NumOpenPrivateConnections = FCString::Atoi(*It.Value());
|
|
// KeysFound++;
|
|
// }
|
|
// else if (FCStringAnsi::Stricmp(TCHAR_TO_ANSI(*It.Key()), STEAMKEY_NUMOPENPUBLICCONNECTIONS) == 0)
|
|
// {
|
|
// Session->NumOpenPublicConnections = FCString::Atoi(*It.Value());
|
|
// KeysFound++;
|
|
// }
|
|
else if (FCStringAnsi::Stricmp(TCHAR_TO_ANSI(*It.Key()), STEAMKEY_P2PADDR) == 0)
|
|
{
|
|
FString KeyValue = It.Value();
|
|
// Remove any protocol flags from the start.
|
|
KeyValue.RemoveFromStart(STEAM_URL_PREFIX);
|
|
|
|
uint64 SteamAddr = FCString::Atoi64(*KeyValue);
|
|
if (SteamAddr != 0)
|
|
{
|
|
SteamP2PAddr->SetSteamID(CSteamID(SteamAddr));
|
|
SteamAddrKeysFound++;
|
|
}
|
|
}
|
|
else if (FCStringAnsi::Stricmp(TCHAR_TO_ANSI(*It.Key()), STEAMKEY_P2PPORT) == 0)
|
|
{
|
|
int32 Port = FCString::Atoi(*It.Value());
|
|
SteamP2PAddr->SetPort(Port);
|
|
SteamAddrKeysFound++;
|
|
}
|
|
else
|
|
{
|
|
FName NewKey;
|
|
FOnlineSessionSetting NewSetting;
|
|
if (SteamKeyToSessionSetting(*It.Key(), TCHAR_TO_ANSI(*It.Value()), NewKey, NewSetting))
|
|
{
|
|
Session->SessionSettings.Set(NewKey, NewSetting);
|
|
}
|
|
else
|
|
{
|
|
bSuccess = false;
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Failed to parse setting from key %s value %s"), *It.Key(), *It.Value());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify success with all required keys found
|
|
// If the user has SteamNetworking off, then we should just check if their host addressing is correct.
|
|
if (bSuccess && (KeysFound == STEAMKEY_NUMREQUIREDSERVERKEYS) && (SteamAddrKeysFound == 2 || (HostAddr.IsValid() && HostAddr->IsValid())))
|
|
{
|
|
SessionInfo->HostAddr = HostAddr;
|
|
SessionInfo->SteamP2PAddr = SteamP2PAddr;
|
|
|
|
Session->SessionInfo = SessionInfo;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Got data on a rule on the server -- you'll get one of these per rule defined on
|
|
* the server you are querying
|
|
*/
|
|
void FPendingSearchResultSteam::RulesResponded(const char *pchRule, const char *pchValue)
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Rules response %s %s"), UTF8_TO_TCHAR(pchRule), UTF8_TO_TCHAR(pchValue));
|
|
ParentQuery->ElapsedTime = 0.0f;
|
|
ServerRules.Add(UTF8_TO_TCHAR(pchRule), UTF8_TO_TCHAR(pchValue));
|
|
}
|
|
|
|
/**
|
|
* The server failed to respond to the request for rule details
|
|
*/
|
|
void FPendingSearchResultSteam::RulesFailedToRespond()
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Rules failed to respond for server"));
|
|
ParentQuery->ElapsedTime = 0.0f;
|
|
RemoveSelf();
|
|
}
|
|
|
|
/**
|
|
* The server has finished responding to the rule details request
|
|
* (ie, you won't get anymore RulesResponded callbacks)
|
|
*/
|
|
void FPendingSearchResultSteam::RulesRefreshComplete()
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Rules refresh complete"));
|
|
ParentQuery->ElapsedTime = 0.0f;
|
|
|
|
// Only append this data if there is an existing search (NULL CurrentSessionSearch implies no active search query)
|
|
FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr<FOnlineSessionSteam>(ParentQuery->Subsystem->GetSessionInterface());
|
|
if (SessionInt.IsValid() && SessionInt->CurrentSessionSearch.IsValid() && SessionInt->CurrentSessionSearch->SearchState == EOnlineAsyncTaskState::InProgress)
|
|
{
|
|
if (FillSessionFromServerRules())
|
|
{
|
|
// Transfer rules to actual search results
|
|
FOnlineSessionSearchResult* SearchResult = new (ParentQuery->SearchSettings->SearchResults) FOnlineSessionSearchResult(PendingSearchResult);
|
|
SearchResult->Session.SessionInfo = PendingSearchResult.Session.SessionInfo;
|
|
if (!SearchResult->IsValid())
|
|
{
|
|
// Remove the failed element
|
|
ParentQuery->SearchSettings->SearchResults.RemoveAtSwap(ParentQuery->SearchSettings->SearchResults.Num() - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
RemoveSelf();
|
|
}
|
|
|
|
/**
|
|
* Remove this search result from the parent's list of pending entries
|
|
*/
|
|
void FPendingSearchResultSteam::RemoveSelf()
|
|
{
|
|
for (int32 SearchIdx=0; SearchIdx< ParentQuery->PendingSearchResults.Num(); SearchIdx++)
|
|
{
|
|
if (ParentQuery->PendingSearchResults[SearchIdx].ServerId == ServerId)
|
|
{
|
|
ParentQuery->PendingSearchResults.RemoveAtSwap(SearchIdx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cancel this rules request
|
|
*/
|
|
void FPendingSearchResultSteam::CancelQuery()
|
|
{
|
|
if (ServerQueryHandle != HSERVERQUERY_INVALID)
|
|
{
|
|
SteamMatchmakingServers()->CancelServerQuery(ServerQueryHandle);
|
|
ServerQueryHandle = HSERVERQUERY_INVALID;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 6385) // Disable dubious Static Analysis warning C6385 on windows. See MS TechNote Here https://developercommunity2.visualstudio.com/t/C6385-False-Positive/878703
|
|
#endif
|
|
|
|
/**
|
|
* Create the proper query for the server list based on the given search settings
|
|
*
|
|
* @param OutFilter Steam structure containing the proper filters
|
|
* @param NumFilters number of filters contained in the array above
|
|
*/
|
|
void FOnlineAsyncTaskSteamFindServerBase::CreateQuery(MatchMakingKeyValuePair_t** OutFilter, int32& NumFilters)
|
|
{
|
|
// Copy the params so we can remove the values as we use them
|
|
FOnlineSearchSettings TempSearchSettings = SearchSettings->QuerySettings;
|
|
|
|
// Include enough space for all search parameters plus the required one "gamedir" below
|
|
int32 MaxFilters = TempSearchSettings.SearchParams.Num() + 1;
|
|
|
|
*OutFilter = new MatchMakingKeyValuePair_t[MaxFilters];
|
|
MatchMakingKeyValuePair_t* Filters = *OutFilter;
|
|
|
|
int32 KeySize = sizeof(Filters[0].m_szKey);
|
|
int32 ValueSize = sizeof(Filters[0].m_szValue);
|
|
|
|
NumFilters = 0;
|
|
// Filter must match at least our game
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szKey, "gamedir", KeySize);
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szValue, UE_PROJECT_STEAMGAMEDIR, ValueSize);
|
|
NumFilters++;
|
|
|
|
FString MapName;
|
|
if (TempSearchSettings.Get(SETTING_MAPNAME, MapName) && !MapName.IsEmpty())
|
|
{
|
|
// Server passes the filter if the server is playing the specified map.
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szKey, "map", KeySize);
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szValue, TCHAR_TO_ANSI(*MapName), ValueSize);
|
|
NumFilters++;
|
|
}
|
|
TempSearchSettings.SearchParams.Remove(SETTING_MAPNAME);
|
|
|
|
FString HostIp;
|
|
if (TempSearchSettings.Get(SEARCH_STEAM_HOSTIP, HostIp) && !HostIp.IsEmpty())
|
|
{
|
|
// Server passes the filter if it passed a valid host ip.
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szKey, "gameaddr", KeySize);
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szValue, TCHAR_TO_ANSI(*HostIp), ValueSize);
|
|
NumFilters++;
|
|
}
|
|
TempSearchSettings.SearchParams.Remove(SEARCH_STEAM_HOSTIP);
|
|
|
|
int32 DedicatedOnly = 0;
|
|
if (TempSearchSettings.Get(SEARCH_DEDICATED_ONLY, DedicatedOnly) && DedicatedOnly != 0)
|
|
{
|
|
// Server passes the filter if it passed true to SetDedicatedServer.
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szKey, "dedicated", KeySize);
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szValue, "true", ValueSize);
|
|
NumFilters++;
|
|
}
|
|
TempSearchSettings.SearchParams.Remove(SEARCH_DEDICATED_ONLY);
|
|
|
|
int32 SecureOnly = 0;
|
|
if (TempSearchSettings.Get(SEARCH_SECURE_SERVERS_ONLY, SecureOnly) && SecureOnly != 0)
|
|
{
|
|
// Server passes the filter if the server is VAC-enabled.
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szKey, "secure", KeySize);
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szValue, "true", ValueSize);
|
|
NumFilters++;
|
|
}
|
|
TempSearchSettings.SearchParams.Remove(SEARCH_SECURE_SERVERS_ONLY);
|
|
|
|
int32 EmptyOnly = 0;
|
|
if (TempSearchSettings.Get(SEARCH_EMPTY_SERVERS_ONLY, EmptyOnly) && EmptyOnly != 0)
|
|
{
|
|
// Server passes the filter if it doesn't have any players.
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szKey, "noplayers", KeySize);
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szValue, "true", ValueSize);
|
|
NumFilters++;
|
|
}
|
|
TempSearchSettings.SearchParams.Remove(SEARCH_EMPTY_SERVERS_ONLY);
|
|
|
|
|
|
// TEMP!!!!
|
|
return;
|
|
#if 0
|
|
/**
|
|
* "full" - not full
|
|
* "empty" - not empty
|
|
* "proxy" - a relay server
|
|
*/
|
|
if (NumFilters <= MaxFilters)
|
|
{
|
|
/** Filter out key value pairs */
|
|
TArray<FString> Clauses;
|
|
FString CurrentClause;
|
|
for (FSearchParams::TConstIterator It(TempSearchSettings.SearchParams); It; ++It)
|
|
{
|
|
const FName Key = It.Key();
|
|
const FOnlineSessionSearchParam& SearchParam = It.Value();
|
|
|
|
FString KeyStr;
|
|
if (SessionKeyToSteamKey(Key, SearchParam.Data, KeyStr))
|
|
{
|
|
if (SearchParam.ComparisonOp == EOnlineComparisonOp::Equals)
|
|
{
|
|
FString NewParam = FString::Printf(TEXT("%s:%s"), *KeyStr, *SearchParam.Data.ToString());
|
|
if (NewParam.Len() <= ValueSize)
|
|
{
|
|
if (NewParam.Len() + CurrentClause.Len() < ValueSize)
|
|
{
|
|
if (CurrentClause.IsEmpty())
|
|
{
|
|
CurrentClause = NewParam;
|
|
}
|
|
else
|
|
{
|
|
// Continue to add to the clause
|
|
CurrentClause = CurrentClause + "," + NewParam;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Create a new clause
|
|
Clauses.Add(CurrentClause);
|
|
CurrentClause = NewParam;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Skipping search clause due to size: %s"), *NewParam);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the remainder clause
|
|
if (!CurrentClause.IsEmpty())
|
|
{
|
|
Clauses.Add(CurrentClause);
|
|
}
|
|
|
|
if (Clauses.Num() > 0)
|
|
{
|
|
// Make sure there is room (Clauses + "and" clause if more than one)
|
|
if (NumFilters + Clauses.Num() + (Clauses.Num() > 1 ? 1 : 0) <= MaxFilters)
|
|
{
|
|
if (Clauses.Num() > 1)
|
|
{
|
|
// "and" (x1 && x2 && ... && xn) where n is number of clauses
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szKey, "and", KeySize);
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szValue, TCHAR_TO_UTF8(*FString::FromInt(Clauses.Num())), ValueSize);
|
|
NumFilters++;
|
|
|
|
for (int32 ClauseIdx=0; ClauseIdx < Clauses.Num(); ClauseIdx++)
|
|
{
|
|
// Server passes the filter if the server's game data (ISteamGameServer::SetGameData) contains all of the
|
|
// specified strings. The value field is a comma-delimited list of strings to match.
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szKey, "gamedataand", KeySize);
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szValue, TCHAR_TO_UTF8(*Clauses[ClauseIdx]), ValueSize);
|
|
NumFilters++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Server passes the filter if the server's game data (ISteamGameServer::SetGameData) contains all of the
|
|
// specified strings. The value field is a comma-delimited list of strings to match.
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szKey, "gamedataand", KeySize);
|
|
FCStringAnsi::Strncpy(Filters[NumFilters].m_szValue, TCHAR_TO_UTF8(*Clauses[0]), ValueSize);
|
|
NumFilters++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
/**
|
|
* Create a search result from a server response
|
|
*
|
|
* @param ServerDetails Steam server details
|
|
*/
|
|
void FOnlineAsyncTaskSteamFindServerBase::ParseSearchResult(class gameserveritem_t* ServerDetails)
|
|
{
|
|
#if WITH_ENGINE
|
|
TSharedRef<FInternetAddr> ServerAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
|
|
|
|
ServerAddr->SetIp(ServerDetails->m_NetAdr.GetIP());
|
|
ServerAddr->SetPort(ServerDetails->m_NetAdr.GetConnectionPort());
|
|
int32 ServerQueryPort = ServerDetails->m_NetAdr.GetQueryPort();
|
|
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Server response IP:%s"), *ServerAddr->ToString(false));
|
|
if (ServerDetails->m_bHadSuccessfulResponse)
|
|
{
|
|
FString GameTags(UTF8_TO_TCHAR(ServerDetails->m_szGameTags));
|
|
|
|
// Check for build compatibility
|
|
int32 ServerBuildId = 0;
|
|
int32 BuildUniqueId = GetBuildUniqueId();
|
|
|
|
TArray<FString> TagArray;
|
|
GameTags.ParseIntoArray(TagArray, TEXT(","), true);
|
|
if (TagArray.Num() > 0 && TagArray[0].StartsWith(STEAMKEY_BUILDUNIQUEID))
|
|
{
|
|
ServerBuildId = FCString::Atoi(*TagArray[0].Mid(UE_ARRAY_COUNT(STEAMKEY_BUILDUNIQUEID)));
|
|
}
|
|
|
|
if (ServerBuildId == BuildUniqueId)
|
|
{
|
|
// Create a new pending search result
|
|
FPendingSearchResultSteam* NewPendingSearch = new FPendingSearchResultSteam(this);
|
|
PendingSearchResults.Add(NewPendingSearch);
|
|
NewPendingSearch->ServerId = FUniqueNetIdSteam::Create(ServerDetails->m_steamID);
|
|
NewPendingSearch->HostAddr = ServerAddr;
|
|
|
|
// Fill search result members
|
|
FOnlineSessionSearchResult* NewSearchResult = &NewPendingSearch->PendingSearchResult;
|
|
NewSearchResult->PingInMs = FMath::Clamp(ServerDetails->m_nPing, 0, MAX_QUERY_PING);
|
|
|
|
// Fill session members
|
|
FOnlineSession* NewSession = &NewSearchResult->Session;
|
|
|
|
//NewSession->OwningUserId = ;
|
|
NewSession->OwningUserName = UTF8_TO_TCHAR(ServerDetails->GetName());
|
|
|
|
NewSession->NumOpenPublicConnections = ServerDetails->m_nMaxPlayers - ServerDetails->m_nPlayers;
|
|
NewSession->NumOpenPrivateConnections = 0;
|
|
|
|
// Fill session settings members
|
|
NewSession->SessionSettings.NumPublicConnections = ServerDetails->m_nMaxPlayers;
|
|
NewSession->SessionSettings.NumPrivateConnections = 0;
|
|
NewSession->SessionSettings.bAntiCheatProtected = ServerDetails->m_bSecure ? true : false;
|
|
NewSession->SessionSettings.Set(SETTING_MAPNAME, FString(UTF8_TO_TCHAR(ServerDetails->m_szMap)), EOnlineDataAdvertisementType::ViaOnlineService);
|
|
|
|
// Start a rules request for this new result
|
|
NewPendingSearch->ServerQueryHandle = SteamMatchmakingServersPtr->ServerRules(ServerDetails->m_NetAdr.GetIP(), ServerQueryPort, NewPendingSearch);
|
|
if (NewPendingSearch->ServerQueryHandle == HSERVERQUERY_INVALID)
|
|
{
|
|
// Remove the failed element
|
|
PendingSearchResults.RemoveAtSwap(PendingSearchResults.Num() - 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Removed incompatible build: ServerBuildUniqueId = 0x%08x, GetBuildUniqueId() = 0x%08x"),
|
|
ServerBuildId, BuildUniqueId);
|
|
}
|
|
}
|
|
#endif //WITH_ENGINE
|
|
}
|
|
|
|
/**
|
|
* Give the async task time to do its work
|
|
* Can only be called on the async task manager thread
|
|
*/
|
|
void FOnlineAsyncTaskSteamFindServerBase::Tick()
|
|
{
|
|
ISteamUtils* SteamUtilsPtr = SteamUtils();
|
|
check(SteamUtilsPtr);
|
|
|
|
if (!bInit)
|
|
{
|
|
SteamMatchmakingServersPtr = SteamMatchmakingServers();
|
|
check(SteamMatchmakingServersPtr);
|
|
|
|
int32 NumFilters = 0;
|
|
MatchMakingKeyValuePair_t* Filters = NULL;
|
|
CreateQuery(&Filters, NumFilters);
|
|
|
|
#if DEBUG_STEAM_FILTERS
|
|
for (int32 FilterIdx=0; FilterIdx<NumFilters; FilterIdx++)
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Verbose, TEXT(" \"%s\" \"%s\" "), UTF8_TO_TCHAR(Filters[FilterIdx].m_szKey), UTF8_TO_TCHAR(Filters[FilterIdx].m_szValue));
|
|
}
|
|
#endif
|
|
|
|
if (SearchSettings->MaxSearchResults <= 0)
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("FOnlineAsyncTaskSteamFindServerBase::Tick - SearchSettings->MaxSearchResults should be greater than 0, but it is currently %d. No search results will be found."), SearchSettings->MaxSearchResults);
|
|
}
|
|
|
|
if (SearchSettings->bIsLanQuery)
|
|
{
|
|
ServerListRequestHandle = SteamMatchmakingServersPtr->RequestLANServerList(Subsystem->GetSteamAppId(), this);
|
|
}
|
|
else
|
|
{
|
|
ServerListRequestHandle = SteamMatchmakingServersPtr->RequestInternetServerList(Subsystem->GetSteamAppId(), &Filters, NumFilters, this);
|
|
}
|
|
|
|
if (ServerListRequestHandle == NULL)
|
|
{
|
|
// Invalid API call
|
|
bIsComplete = true;
|
|
bWasSuccessful = false;
|
|
}
|
|
|
|
// Preallocate space for results
|
|
PendingSearchResults.Empty(SearchSettings->MaxSearchResults);
|
|
|
|
delete [] Filters;
|
|
bInit = true;
|
|
}
|
|
|
|
ElapsedTime += 1.0f/16.0f;
|
|
|
|
// Cancel query when we've reached our requested limit
|
|
bool bReachedSearchLimit = (SearchSettings->SearchResults.Num() >= SearchSettings->MaxSearchResults) ? true : false;
|
|
// Check for activity timeout
|
|
float AsyncTimeout = ASYNC_TASK_TIMEOUT;
|
|
GConfig->GetFloat(TEXT("OnlineSubsystemSteam"), TEXT("OnlineAsyncTaskSteamFindServerBaseTimeout"), AsyncTimeout, GEngineIni);
|
|
|
|
bool bTimedOut = (ElapsedTime >= AsyncTimeout) ? true : false;
|
|
// Check for proper completion
|
|
bool bServerSearchComplete = (bServerRefreshComplete && PendingSearchResults.Num() == 0) ? true : false;
|
|
if ( bReachedSearchLimit || bTimedOut || bServerSearchComplete)
|
|
{
|
|
bIsComplete = true;
|
|
bWasSuccessful = true;
|
|
}
|
|
|
|
if (bIsComplete)
|
|
{
|
|
// Cancel further server queries (may trigger RefreshComplete delegate)
|
|
if (ServerListRequestHandle != NULL)
|
|
{
|
|
SteamMatchmakingServersPtr->CancelQuery(ServerListRequestHandle);
|
|
SteamMatchmakingServersPtr->ReleaseRequest(ServerListRequestHandle);
|
|
ServerListRequestHandle = NULL;
|
|
}
|
|
|
|
// Cancel further rules queries
|
|
for (int32 PendingIdx=0; PendingIdx<PendingSearchResults.Num(); ++PendingIdx)
|
|
{
|
|
PendingSearchResults[PendingIdx].CancelQuery();
|
|
}
|
|
PendingSearchResults.Empty();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the SteamAPI when a server has successfully responded
|
|
*/
|
|
void FOnlineAsyncTaskSteamFindServerBase::ServerResponded(HServerListRequest Request, int iServer)
|
|
{
|
|
ElapsedTime = 0.0f;
|
|
|
|
gameserveritem_t* Server = SteamMatchmakingServersPtr->GetServerDetails(Request, iServer);
|
|
if (Server != NULL)
|
|
{
|
|
// Filter out servers that don't match our appid here
|
|
if (!Server->m_bDoNotRefresh && Server->m_nAppID == SteamUtils()->GetAppID())
|
|
{
|
|
ParseSearchResult(Server);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the SteamAPI when a server has failed to respond
|
|
*/
|
|
void FOnlineAsyncTaskSteamFindServerBase::ServerFailedToRespond(HServerListRequest Request, int iServer)
|
|
{
|
|
#if WITH_ENGINE
|
|
ElapsedTime = 0.0f;
|
|
|
|
gameserveritem_t* Server = SteamMatchmakingServersPtr->GetServerDetails(Request, iServer);
|
|
if (Server != NULL)
|
|
{
|
|
TSharedRef<FInternetAddr> ServerAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
|
|
int32 ServerQueryPort = 0;
|
|
|
|
ServerAddr->SetIp(Server->m_NetAdr.GetIP());
|
|
ServerAddr->SetPort(Server->m_NetAdr.GetConnectionPort());
|
|
ServerQueryPort = Server->m_NetAdr.GetQueryPort();
|
|
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Failed to respond IP:%s"), *ServerAddr->ToString(false));
|
|
|
|
// Filter out servers that don't match our appid here
|
|
if (Server->m_nAppID == SteamUtils()->GetAppID())
|
|
{
|
|
}
|
|
}
|
|
#endif //WITH_ENGINE
|
|
}
|
|
|
|
/**
|
|
* Called by the SteamAPI when all server requests for the list have completed
|
|
*/
|
|
void FOnlineAsyncTaskSteamFindServerBase::RefreshComplete(HServerListRequest Request, EMatchMakingServerResponse Response)
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Verbose, TEXT("Server query complete %s"), *SteamMatchMakingServerResponseString(Response));
|
|
bServerRefreshComplete = true;
|
|
ElapsedTime = 0.0f;
|
|
}
|
|
|
|
/**
|
|
* Give the async task a chance to marshal its data back to the game thread
|
|
* Can only be called on the game thread by the async task manager
|
|
*/
|
|
void FOnlineAsyncTaskSteamFindServerBase::Finalize()
|
|
{
|
|
FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr<FOnlineSessionSteam>(Subsystem->GetSessionInterface());
|
|
|
|
SearchSettings->SearchState = bWasSuccessful ? EOnlineAsyncTaskState::Done : EOnlineAsyncTaskState::Failed;
|
|
if (bWasSuccessful)
|
|
{
|
|
if (SearchSettings->SearchResults.Num() > 0)
|
|
{
|
|
// Allow game code to sort the servers
|
|
SearchSettings->SortSearchResults();
|
|
}
|
|
}
|
|
|
|
if (SessionInt->CurrentSessionSearch.IsValid() && SearchSettings == SessionInt->CurrentSessionSearch)
|
|
{
|
|
SessionInt->CurrentSessionSearch = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a human readable description of task
|
|
*/
|
|
FString FOnlineAsyncTaskSteamFindServerForInviteSession::ToString() const
|
|
{
|
|
return FString::Printf(TEXT("FOnlineAsyncTaskSteamFindServerForInvite bWasSuccessful: %d Results: %d"), WasSuccessful(), SearchSettings->SearchResults.Num());
|
|
}
|
|
|
|
|
|
/**
|
|
* Async task is given a chance to trigger it's delegates
|
|
*/
|
|
void FOnlineAsyncTaskSteamFindServerForInviteSession::TriggerDelegates()
|
|
{
|
|
if (FindServerInviteCompleteWithUserIdDelegates.IsBound() && LocalUserNum >= 0)
|
|
{
|
|
if (bWasSuccessful && SearchSettings->SearchResults.Num() > 0)
|
|
{
|
|
FindServerInviteCompleteWithUserIdDelegates.Broadcast(bWasSuccessful, LocalUserNum, FUniqueNetIdSteam::Create(SteamUser()->GetSteamID()), SearchSettings->SearchResults[0]);
|
|
}
|
|
else
|
|
{
|
|
FOnlineSessionSearchResult EmptyResult;
|
|
FindServerInviteCompleteWithUserIdDelegates.Broadcast(bWasSuccessful, LocalUserNum, FUniqueNetIdSteam::Create(SteamUser()->GetSteamID()), EmptyResult);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a human readable description of task
|
|
*/
|
|
FString FOnlineAsyncTaskSteamFindServerForFriendSession::ToString() const
|
|
{
|
|
return FString::Printf(TEXT("FOnlineAsyncTaskSteamFindServerForFriend bWasSuccessful: %d Results: %d"), WasSuccessful(), SearchSettings->SearchResults.Num());
|
|
}
|
|
|
|
/**
|
|
* Async task is given a chance to trigger it's delegates
|
|
*/
|
|
void FOnlineAsyncTaskSteamFindServerForFriendSession::TriggerDelegates()
|
|
{
|
|
if (FindServerInviteCompleteDelegates.IsBound() && LocalUserNum >= 0)
|
|
{
|
|
if (bWasSuccessful && SearchSettings->SearchResults.Num() > 0)
|
|
{
|
|
FindServerInviteCompleteDelegates.Broadcast(LocalUserNum, bWasSuccessful, SearchSettings->SearchResults);
|
|
}
|
|
else
|
|
{
|
|
TArray<FOnlineSessionSearchResult> EmptyResult;
|
|
FindServerInviteCompleteDelegates.Broadcast(LocalUserNum, bWasSuccessful, EmptyResult);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a human readable description of task
|
|
*/
|
|
FString FOnlineAsyncTaskSteamFindServers::ToString() const
|
|
{
|
|
return FString::Printf(TEXT("FOnlineAsyncTaskSteamFindServers bWasSuccessful: %d Results: %d"), WasSuccessful(), SearchSettings->SearchResults.Num());
|
|
}
|
|
|
|
/**
|
|
* Async task is given a chance to trigger it's delegates
|
|
*/
|
|
void FOnlineAsyncTaskSteamFindServers::TriggerDelegates()
|
|
{
|
|
if (FindServersCompleteDelegates.IsBound())
|
|
{
|
|
FindServersCompleteDelegates.Broadcast(bWasSuccessful);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Give the async task a chance to marshal its data back to the game thread
|
|
* Can only be called on the game thread by the async task manager
|
|
*/
|
|
void FOnlineAsyncEventSteamInviteAccepted::Finalize()
|
|
{
|
|
FOnlineSessionSteamPtr SessionInt = StaticCastSharedPtr<FOnlineSessionSteam>(Subsystem->GetSessionInterface());
|
|
if (SessionInt.IsValid() && !SessionInt->CurrentSessionSearch.IsValid())
|
|
{
|
|
// Create a search settings object
|
|
TSharedRef<FOnlineSessionSearch> SearchSettings = MakeShareable(new FOnlineSessionSearch());
|
|
SessionInt->CurrentSessionSearch = SearchSettings;
|
|
SessionInt->CurrentSessionSearch->SearchState = EOnlineAsyncTaskState::InProgress;
|
|
|
|
TCHAR ParsedURL[1024];
|
|
if (!FParse::Value(*ConnectionURL, TEXT("SteamConnectIP="), ParsedURL, UE_ARRAY_COUNT(ParsedURL)))
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("FOnlineAsyncEventSteamInviteAccepted: Failed to parse connection URL"));
|
|
return;
|
|
}
|
|
|
|
// Determine the port
|
|
int32 Port = 0;
|
|
TCHAR* PortToken = FCString::Strchr(ParsedURL, ':');
|
|
if (PortToken)
|
|
{
|
|
Port = FCString::Atoi(PortToken+1);
|
|
PortToken[0] = '\0';
|
|
}
|
|
|
|
Port = (Port > 0) ? Port : Subsystem->GetGameServerGamePort();
|
|
|
|
// Parse the address
|
|
#if WITH_ENGINE
|
|
TSharedPtr<FInternetAddr> IpAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetAddressFromString(ParsedURL);
|
|
if (IpAddr.IsValid())
|
|
{
|
|
SessionInt->CurrentSessionSearch->QuerySettings.Set(FName(SEARCH_STEAM_HOSTIP), IpAddr->ToString(false), EOnlineComparisonOp::Equals);
|
|
FOnlineAsyncTaskSteamFindServerForInviteSession* NewTask = new FOnlineAsyncTaskSteamFindServerForInviteSession(Subsystem, SearchSettings, LocalUserNum, SessionInt->OnSessionUserInviteAcceptedDelegates);
|
|
Subsystem->QueueAsyncTask(NewTask);
|
|
}
|
|
#else //WITH_ENGINE
|
|
ensure(false); //Launch invites are not expected to work without engine
|
|
#endif //WITH_ENGINE
|
|
}
|
|
else
|
|
{
|
|
UE_LOG_ONLINE_SESSION(Warning, TEXT("Invalid session or search already in progress when accepting invite. Ignoring invite request."));
|
|
}
|
|
}
|
|
|