// Copyright Epic Games, Inc. All Rights Reserved. #include "OnlineSubsystemTencent.h" #if WITH_TENCENTSDK #include "Interfaces/IMessageSanitizerInterface.h" #include "OnlineAsyncTaskManagerTencent.h" #include "Interfaces/OnlineEntitlementsInterface.h" #include "OnlineIdentityTencent.h" #include "HAL/RunnableThread.h" #include "Interfaces/OnlineExternalUIInterface.h" #include "OnlineIdentityTencent.h" #include "Interfaces/OnlineFriendsInterface.h" #include "OnlineSubsystemTencent.h" #include "Interfaces/OnlineTitleFileInterface.h" #include "Interfaces/OnlineUserCloudInterface.h" #include "Interfaces/VoiceInterface.h" #include "OnlineSessionTencent.h" #include "Trace/Trace.inl" #if WITH_TENCENT_RAIL_SDK #include "Features/IModularFeatures.h" #include "Interfaces/OnlinePlayTimeLimit.h" #include "Misc/CommandLine.h" #include "Misc/ConfigCacheIni.h" #include "OnlineExternalUITencent.h" #include "OnlineFriendsTencent.h" #include "OnlineMessageSanitizerTencent.h" #include "OnlinePlayTimeLimitTencent.h" #include "OnlinePresenceTencent.h" #include "OnlinePurchaseTencent.h" #include "OnlineSessionTencentRail.h" #include "OnlineStoreTencent.h" #include "OnlineSubsystemTencentPrivate.h" #include "OnlineUserTencent.h" #include "PlayTimeLimitImpl.h" #endif IOnlineSessionPtr FOnlineSubsystemTencent::GetSessionInterface() const { return TencentSession; } IOnlineFriendsPtr FOnlineSubsystemTencent::GetFriendsInterface() const { #if WITH_TENCENT_RAIL_SDK return TencentFriends; #else return nullptr; #endif } IOnlinePartyPtr FOnlineSubsystemTencent::GetPartyInterface() const { return nullptr; } IOnlineGroupsPtr FOnlineSubsystemTencent::GetGroupsInterface() const { return nullptr; } IOnlineSharedCloudPtr FOnlineSubsystemTencent::GetSharedCloudInterface() const { return nullptr; } IOnlineUserCloudPtr FOnlineSubsystemTencent::GetUserCloudInterface() const { return nullptr; } IOnlineEntitlementsPtr FOnlineSubsystemTencent::GetEntitlementsInterface() const { return nullptr; } IOnlineLeaderboardsPtr FOnlineSubsystemTencent::GetLeaderboardsInterface() const { return nullptr; } IOnlineVoicePtr FOnlineSubsystemTencent::GetVoiceInterface() const { return nullptr; } IOnlineExternalUIPtr FOnlineSubsystemTencent::GetExternalUIInterface() const { #if WITH_TENCENT_RAIL_SDK return TencentExternalUI; #else return nullptr; #endif } IOnlineTimePtr FOnlineSubsystemTencent::GetTimeInterface() const { return nullptr; } IOnlineIdentityPtr FOnlineSubsystemTencent::GetIdentityInterface() const { return TencentIdentity; } IOnlineTitleFilePtr FOnlineSubsystemTencent:: GetTitleFileInterface() const { return nullptr; } IOnlineStoreV2Ptr FOnlineSubsystemTencent::GetStoreV2Interface() const { #if WITH_TENCENT_RAIL_SDK return TencentStore; #else return nullptr; #endif } IOnlinePurchasePtr FOnlineSubsystemTencent::GetPurchaseInterface() const { #if WITH_TENCENT_RAIL_SDK return TencentPurchase; #else return nullptr; #endif } IOnlineEventsPtr FOnlineSubsystemTencent::GetEventsInterface() const { return nullptr; } IOnlineAchievementsPtr FOnlineSubsystemTencent::GetAchievementsInterface() const { return nullptr; } IOnlineSharingPtr FOnlineSubsystemTencent::GetSharingInterface() const { return nullptr; } IOnlineUserPtr FOnlineSubsystemTencent::GetUserInterface() const { #if WITH_TENCENT_RAIL_SDK return TencentUser; #else return nullptr; #endif } IOnlineMessagePtr FOnlineSubsystemTencent::GetMessageInterface() const { return nullptr; } IOnlinePresencePtr FOnlineSubsystemTencent::GetPresenceInterface() const { #if WITH_TENCENT_RAIL_SDK return TencentPresence; #else return nullptr; #endif } IOnlineChatPtr FOnlineSubsystemTencent::GetChatInterface() const { return nullptr; } IOnlineStatsPtr FOnlineSubsystemTencent::GetStatsInterface() const { return nullptr; } IOnlineTurnBasedPtr FOnlineSubsystemTencent::GetTurnBasedInterface() const { return nullptr; } IOnlineTournamentPtr FOnlineSubsystemTencent::GetTournamentInterface() const { return nullptr; } void FOnlineSubsystemTencent::QueueAsyncTask(FOnlineAsyncTask* AsyncTask) { check(OnlineAsyncTaskThreadRunnable); OnlineAsyncTaskThreadRunnable->AddToInQueue(AsyncTask); } void FOnlineSubsystemTencent::QueueAsyncOutgoingItem(FOnlineAsyncItem* AsyncItem) { check(OnlineAsyncTaskThreadRunnable); OnlineAsyncTaskThreadRunnable->AddToOutQueue(AsyncItem); } void FOnlineSubsystemTencent::QueueAsyncParallelTask(FOnlineAsyncTask* AsyncTask) { check(OnlineAsyncTaskThreadRunnable); OnlineAsyncTaskThreadRunnable->AddToParallelTasks(AsyncTask); } #if WITH_TENCENT_RAIL_SDK void FOnlineSubsystemTencent::AddToMetadataCache(const TMap& InMetadata) { MetadataDataCache.Append(InMetadata); } #endif FText FOnlineSubsystemTencent::GetOnlineServiceName() const { return NSLOCTEXT("FOnlineSubsystemTencent", "OnlineServiceName", "Tencent"); } FText FOnlineSubsystemTencent::GetSocialPlatformName() const { return NSLOCTEXT("FOnlineSubsystemTencent", "SocialPlatformName", "WeGame Friends"); } bool FOnlineSubsystemTencent::Init() { UE_LOG_ONLINE(Verbose, TEXT("FOnlineSubsystemTencent::Init() Name: %s"), *InstanceName.ToString()); // Initialize Rail SDK if (!InitRailSdk()) { UE_LOG_ONLINE(Warning, TEXT("Failed to initialize Rail")); return false; } #if WITH_TENCENT_RAIL_SDK if (IModularFeatures::Get().IsModularFeatureAvailable(IOnlinePlayTimeLimit::GetModularFeatureName())) { // Register delegate to create users for the Anti-Addiction system. FPlayTimeLimitImpl::Get().OnRequestCreateUser.BindLambda([](const FUniqueNetId& UserId)->FPlayTimeLimitUser* { return new FOnlinePlayTimeLimitUserTencentRail(UserId.AsShared()); }); } #endif bool bInitSuccess = true; // Create the online async task thread (after RAIL to register events) OnlineAsyncTaskThreadRunnable = new FOnlineAsyncTaskManagerTencent(this); check(OnlineAsyncTaskThreadRunnable); OnlineAsyncTaskThread = FRunnableThread::Create(OnlineAsyncTaskThreadRunnable, *FString::Printf(TEXT("OnlineAsyncTaskThreadTencent %s"), *InstanceName.ToString()), 128 * 1024, TPri_Normal); check(OnlineAsyncTaskThread); UE_LOG_ONLINE(Verbose, TEXT("Created thread (ID:%d)."), OnlineAsyncTaskThread->GetThreadID()); TencentIdentity = MakeShared(this); #if WITH_TENCENT_RAIL_SDK TencentSession = MakeShared(this); if (!TencentSession->Init()) { UE_LOG_ONLINE(Warning, TEXT("Failed to initialize session interface")); TencentSession.Reset(); bInitSuccess = false; } TencentFriends = MakeShared(this); if (!TencentFriends->Init()) { UE_LOG_ONLINE(Warning, TEXT("Failed to initialize friends interface")); TencentFriends.Reset(); bInitSuccess = false; } TencentPresence = MakeShared(this); if (!TencentPresence->Init()) { UE_LOG_ONLINE(Warning, TEXT("Failed to initialize presence interface")); TencentPresence.Reset(); bInitSuccess = false; } TencentExternalUI = MakeShared(this); TencentUser = MakeShared(this); TencentMessageSanitizer = MakeShared(this); TencentPurchase = MakeShared(this); TencentStore = MakeShared(this); #endif // update services based on user login/logout events OnLoginChangedHandle = TencentIdentity->AddOnLoginChangedDelegate_Handle(FOnLoginChangedDelegate::CreateThreadSafeSP(this, &FOnlineSubsystemTencent::OnLoginChanged)); return bInitSuccess; } /** * @param 'uint32_t' is the security level, defined in RailWarningMessageLevel * @param 'const char*' is the message */ void RailWarningMessageCallback(uint32_t Level, const char* Msg) { UE_LOG_ONLINE(Warning, TEXT("RailWarning: [%d] %s"), Level, Msg ? UTF8_TO_TCHAR(Msg) : TEXT("NoWarning")); } bool FOnlineSubsystemTencent::UsesRailSdk() const { const bool bUsesRailSdk = WITH_TENCENT_RAIL_SDK && !IsDedicated(); return bUsesRailSdk; } bool FOnlineSubsystemTencent::InitRailSdk() { bool bResult = false; if (UsesRailSdk()) { #if WITH_TENCENT_RAIL_SDK // Make sure the Rail SDK (Wegame) dll is loaded/initialized RailSdkWrapper& Wrapper = RailSdkWrapper::Get(); if (Wrapper.Load()) { // Game id config FString RailGameIdStr; if (GConfig->GetString(TEXT("OnlineSubsystemTencent"), TEXT("RailGameId"), RailGameIdStr, GEngineIni) && !RailGameIdStr.IsEmpty()) { LexFromString(RailGameId, *RailGameIdStr); const bool bRailNeedsRestart = Wrapper.RailNeedRestartAppForCheckingEnvironment(rail::RailGameID(RailGameId)); if (!bRailNeedsRestart) { if (Wrapper.RailInitialize()) { rail::IRailFactory* const RailFactory = Wrapper.RailFactory(); if (RailFactory) { rail::IRailUtils* const RailUtils = RailFactory->RailUtils(); if (RailUtils) { RailUtils->SetWarningMessageCallback(RailWarningMessageCallback); } // Log if we are not Online rail::IRailSystemHelper* const RailSystemHelper = RailFactory->RailSystemHelper(); if (RailSystemHelper) { const rail::RailSystemState SystemState = RailSystemHelper->GetPlatformSystemState(); switch (SystemState) { case rail::RailSystemState::kSystemStatePlatformOnline: break; default: UE_LOG_ONLINE(Warning, TEXT("InitRailSdk: Rail Platform System State is %s, this will impact your ability to login"), *LexToString(SystemState)); } } // Clear my metadata on startup in case of a previous crash while we load rail::IRailFriends* Friends = RailFactory->RailFriends(); if (Friends) { // Presence and session interfaces need to clear this data on shutdown rail::RailResult RailResult = Friends->AsyncClearAllMyMetadata(rail::RailString()); } } bResult = true; } else { UE_LOG_ONLINE(Warning, TEXT("RailInitialize failed with RailGameid=%llu"), RailGameId); } } else { UE_LOG_ONLINE(Warning, TEXT("RailNeedRestartAppForCheckingEnvironment failed with RailGameid=%llu"), RailGameId); #if !UE_BUILD_SHIPPING && !UE_BUILD_TEST if (FCString::Strstr(FCommandLine::Get(), TEXT("rail_debug_mode"))) { UE_LOG_ONLINE(Warning, TEXT("When running with --rail_debug_mode, be sure you are running the game as an administrator")); } else { UE_LOG_ONLINE(Warning, TEXT("Was the game launched through WeGame?")) } #endif } } else { UE_LOG_ONLINE(Warning, TEXT("Invalid RailGameId. Please set [OnlineSubsystemTencent] RailGameId= in your Engine.ini")); } } else { UE_LOG_ONLINE(Warning, TEXT("Failed to load the Rail SDK Wrapper")); } #endif } else { // no errors if we don't use RailSDK bResult = true; } return bResult; } void FOnlineSubsystemTencent::ShutdownRailSdk() { if (UsesRailSdk()) { #if WITH_TENCENT_RAIL_SDK RailSdkWrapper& Wrapper = RailSdkWrapper::Get(); if (Wrapper.IsInitialized()) { rail::IRailFactory* RailFactory = Wrapper.RailFactory(); if (RailFactory) { rail::IRailFriends* Friends = RailFactory->RailFriends(); if (Friends) { // Presence and session interfaces need to clear this data on shutdown rail::RailResult RailResult = Friends->AsyncClearAllMyMetadata(rail::RailString()); } } } Wrapper.RailFinalize(); Wrapper.Shutdown(); #endif } } void FOnlineSubsystemTencent::OnLoginChanged(int32 LocalUserNum) { } void FOnlineSubsystemTencent::PreUnload() { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineSubsystemTencent::Preunload() Name: %s"), *InstanceName.ToString()); } bool FOnlineSubsystemTencent::Shutdown() { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineSubsystemTencent::Shutdown() Name: %s"), *InstanceName.ToString()); FOnlineSubsystemImpl::Shutdown(); #if WITH_TENCENT_RAIL_SDK if (TencentPresence.IsValid()) { TencentPresence->Shutdown(); } #endif if (TencentSession.IsValid()) { TencentSession->Shutdown(); } if (TencentIdentity.IsValid()) { TencentIdentity->ClearOnLoginChangedDelegate_Handle(OnLoginChangedHandle); } #define DESTRUCT_INTERFACE(Interface) \ if (Interface.IsValid()) \ { \ ensure(Interface.IsUnique()); \ Interface.Reset(); \ } // Destruct the interfaces #if WITH_TENCENT_RAIL_SDK DESTRUCT_INTERFACE(TencentStore); DESTRUCT_INTERFACE(TencentPurchase); DESTRUCT_INTERFACE(TencentUser); DESTRUCT_INTERFACE(TencentExternalUI); DESTRUCT_INTERFACE(TencentPresence); DESTRUCT_INTERFACE(TencentFriends); DESTRUCT_INTERFACE(TencentMessageSanitizer); #endif DESTRUCT_INTERFACE(TencentSession); DESTRUCT_INTERFACE(TencentIdentity); if (OnlineAsyncTaskThread) { // Destroy the online async task thread delete OnlineAsyncTaskThread; OnlineAsyncTaskThread = nullptr; } if (OnlineAsyncTaskThreadRunnable) { delete OnlineAsyncTaskThreadRunnable; OnlineAsyncTaskThreadRunnable = nullptr; } #if WITH_TENCENT_RAIL_SDK // Shutdown / unload the Rail SDK (Wegame) ShutdownRailSdk(); #endif return true; } bool FOnlineSubsystemTencent::Tick(float DeltaTime) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FOnlineSubsystemTencent_Tick); if (!FOnlineSubsystemImpl::Tick(DeltaTime)) { return false; } if (OnlineAsyncTaskThreadRunnable) { OnlineAsyncTaskThreadRunnable->GameTick(); } if (TencentSession.IsValid()) { TencentSession->Tick(DeltaTime); } return true; } FString FOnlineSubsystemTencent::GetAppId() const { FString AppId; #if WITH_TENCENT_RAIL_SDK AppId = LexToString(RailGameId); #endif return AppId; } bool FOnlineSubsystemTencent::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { if (FOnlineSubsystemImpl::Exec(InWorld, Cmd, Ar)) { return true; } bool bWasHandled = false; if (FParse::Command(&Cmd, TEXT("TEST"))) { #if WITH_DEV_AUTOMATION_TESTS if (FParse::Command(&Cmd, TEXT("AUTH"))) { bWasHandled = HandleAuthExecCommands(InWorld, Cmd, Ar); } #endif // WITH_DEV_AUTOMATION_TESTS } else if (FParse::Command(&Cmd, TEXT("SESSION"))) { bWasHandled = HandleSessionExecCommands(InWorld, Cmd, Ar); } else if (FParse::Command(&Cmd, TEXT("PRESENCE"))) { bWasHandled = HandlePresenceExecCommands(InWorld, Cmd, Ar); } else if (FParse::Command(&Cmd, TEXT("USERS"))) { bWasHandled = HandleUsersExecCommands(InWorld, Cmd, Ar); } else if (FParse::Command(&Cmd, TEXT("FRIENDS"))) { bWasHandled = HandleFriendExecCommands(InWorld, Cmd, Ar); } else if (FParse::Command(&Cmd, TEXT("RAILSDKWRAPPER"))) { bWasHandled = HandleRailSdkWrapperExecCommands(InWorld, Cmd, Ar); } else if (FParse::Command(&Cmd, TEXT("DUMPKEYS"))) { #if WITH_TENCENT_RAIL_SDK bool bLocalUser = true; FUniqueNetIdPtr UserId = nullptr; FString UserIdStr = FParse::Token(Cmd, true); if (!UserIdStr.IsEmpty()) { UserId = FUniqueNetIdRail::Create(UserIdStr); bLocalUser = false; } else { UserId = GetFirstSignedInUser(TencentIdentity); } if (UserId.IsValid()) { TArray OutDebugKeys; MetadataDataCache.GenerateKeyArray(OutDebugKeys); FUniqueNetIdRailPtr UserIdRail = StaticCastSharedPtr(UserId); FOnlineAsyncTaskRailGetUserMetadata* InnerTask = new FOnlineAsyncTaskRailGetUserMetadata(this, *UserIdRail, OutDebugKeys, FOnOnlineAsyncTaskRailGetUserMetadataComplete::CreateLambda([this, bLocalUser, OutDebugKeys](const FGetUserMetadataTaskResult& Result) { if (Result.Error.WasSuccessful()) { UE_LOG_ONLINE(Display, TEXT("Metadata [%s]"), *Result.UserId->ToDebugString()); UE_LOG_ONLINE(Display, TEXT("- Keys")); for (const TPair& Pair : Result.Metadata) { UE_LOG_ONLINE(Display, TEXT(" - [%s] %s"), *Pair.Key, *Pair.Value.ToString()); } if (bLocalUser) { for (const FString& DebugKey : OutDebugKeys) { int32 SuffixIdx = INDEX_NONE; if (DebugKey.FindLastChar(TEXT('_'), SuffixIdx)) { FString Key = DebugKey.Left(SuffixIdx); const FVariantData* Value = Result.Metadata.Find(Key); if (Value) { // Compare to cached value FString* CachedValue = MetadataDataCache.Find(DebugKey); if (CachedValue && (*CachedValue != Value->ToString())) { UE_LOG_ONLINE(Display, TEXT("Key does not match: %s [%s]:[%s]"), *DebugKey, **CachedValue, *Value->ToString()); } else if (!CachedValue) { UE_LOG_ONLINE(Display, TEXT("Cache does not contain key: %s"), *DebugKey); } } else { UE_LOG_ONLINE(Display, TEXT("Result does not contain key: %s"), *DebugKey); } } } } } else { UE_LOG_ONLINE(Display, TEXT("Failed to get metadata")); } })); QueueAsyncTask(InnerTask); } #endif bWasHandled = true; } else if (FParse::Command(&Cmd, TEXT("AAS"))) { UE_LOG_ONLINE(Warning, TEXT("AAS is no longer in OnlineSubsystemTencent, replace everything from the beginning of the command up to AAS with 'PlayTimeLimit' for example 'PlayTimeLimit NOTIFYNOW'")); } return bWasHandled; } bool FOnlineSubsystemTencent::HandleAuthExecCommands(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { bool bWasHandled = false; #if WITH_DEV_AUTOMATION_TESTS if (FParse::Command(&Cmd, TEXT("INFO"))) { FString AuthType = FParse::Token(Cmd, false); bWasHandled = true; } #endif // WITH_DEV_AUTOMATION_TESTS return bWasHandled; } bool FOnlineSubsystemTencent::HandleSessionExecCommands(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { bool bWasHandled = false; #if WITH_TENCENT_RAIL_SDK if (FParse::Command(&Cmd, TEXT("DUMPMETADATA"))) { // Retrieves session metadata based on the invite command line keys (tests individual steps, same as FOnlineAsyncTaskRailGetUserInvite) FUniqueNetIdPtr UserId = GetFirstSignedInUser(TencentIdentity); if (UserId.IsValid()) { FUniqueNetIdRailPtr UserIdRail = StaticCastSharedPtr(UserId); TWeakPtr LocalWeakThis(AsShared()); FOnlineAsyncTaskRailGetInviteCommandline* OuterTask = new FOnlineAsyncTaskRailGetInviteCommandline(this, *UserIdRail, FOnOnlineAsyncTaskRailGetInviteCommandLineComplete::CreateLambda([LocalWeakThis, UserIdRail](const FGetInviteCommandLineTaskResult& OuterResult) { FOnlineSubsystemTencentPtr StrongThis = StaticCastSharedPtr(LocalWeakThis.Pin()); if (StrongThis.IsValid()) { TArray Keys; if (OuterResult.Error.WasSuccessful()) { FString CommandLineCopy = OuterResult.Commandline; CommandLineCopy.ParseIntoArray(Keys, RAIL_METADATA_KEY_SEPARATOR); FOnlineAsyncTaskRailGetUserMetadata* InnerTask = new FOnlineAsyncTaskRailGetUserMetadata(StrongThis.Get(), *UserIdRail, Keys, FOnOnlineAsyncTaskRailGetUserMetadataComplete::CreateLambda([UserIdRail, CommandLineCopy](const FGetUserMetadataTaskResult& InnerResult) { if (InnerResult.Error.WasSuccessful()) { UE_LOG_ONLINE(Display, TEXT("Metadata [%s]"), *UserIdRail->ToDebugString()); UE_LOG_ONLINE(Display, TEXT("- Commandline: %s"), *CommandLineCopy); UE_LOG_ONLINE(Display, TEXT("- Keys")); for (const TPair& Pair : InnerResult.Metadata) { UE_LOG_ONLINE(Display, TEXT(" - [%s] %s"), *Pair.Key, *Pair.Value.ToString()); } } else { UE_LOG_ONLINE(Display, TEXT("Failed to get metadata")); } })); StrongThis->QueueAsyncTask(InnerTask); } else { UE_LOG_ONLINE(Display, TEXT("Failed to get command line metadata")); } } })); QueueAsyncTask(OuterTask); } bWasHandled = true; } else if (FParse::Command(&Cmd, TEXT("METAINVITE"))) { // Retrieve a session invite based on the local users session data FOnlineSessionTencentRailPtr TencentSessionRail = StaticCastSharedPtr(TencentSession); if (TencentSessionRail.IsValid()) { FUniqueNetIdPtr UserId = GetFirstSignedInUser(TencentIdentity); if (UserId.IsValid()) { FUniqueNetIdRailPtr UserIdRail = StaticCastSharedPtr(UserId); TWeakPtr LocalWeakThis(AsShared()); FOnOnlineAsyncTaskRailGetUserInviteComplete CompletionDelegate = FOnOnlineAsyncTaskRailGetUserInviteComplete::CreateLambda([LocalWeakThis, UserIdRail](const FGetUserInviteTaskResult& Result) { FOnlineSubsystemTencentPtr StrongThis = StaticCastSharedPtr(LocalWeakThis.Pin()); if (StrongThis.IsValid()) { if (Result.Error.WasSuccessful()) { UE_LOG_ONLINE(Display, TEXT("Metadata [%s]"), *UserIdRail->ToDebugString()); UE_LOG_ONLINE(Display, TEXT("- Commandline: %s"), *Result.Commandline); UE_LOG_ONLINE(Display, TEXT("- Keys")); for (const TPair& Pair : Result.Metadata) { UE_LOG_ONLINE(Display, TEXT(" - [%s] %s"), *Pair.Key, *Pair.Value.ToString()); } } else { UE_LOG_ONLINE(Display, TEXT("Failed to get invite")); } } }); FOnlineAsyncTaskRailGetUserInvite* NewTask = new FOnlineAsyncTaskRailGetUserInvite(this, *UserIdRail, CompletionDelegate); QueueAsyncTask(NewTask); } } bWasHandled = true; } #endif //WITH_TENCENT_RAIL_SDK return bWasHandled; } bool FOnlineSubsystemTencent::HandlePresenceExecCommands(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { bool bWasHandled = false; #if WITH_TENCENT_RAIL_SDK if (FParse::Command(&Cmd, TEXT("REPORTPLAYERS"))) { TArray ReportedPlayers; FString UserIdStr = FParse::Token(Cmd, true); FString PresenceStr = FParse::Token(Cmd, true); if (!UserIdStr.IsEmpty()) { FUniqueNetIdRef ReportUserId(FUniqueNetIdRail::Create(UserIdStr)); if (ReportUserId->IsValid()) { ReportedPlayers.Emplace(ReportUserId, PresenceStr); } else { UE_LOG_ONLINE_FRIEND(Warning, TEXT("Invalid rail user specified")); } } else { if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { rail::RailArray Friends; rail::RailResult Result = RailFriends->GetFriendsList(&Friends); if (Result == rail::kSuccess) { for (uint32 RailIdx = 0; RailIdx < Friends.size(); ++RailIdx) { const rail::RailFriendInfo& RailFriendInfo(Friends[RailIdx]); if (RailFriendInfo.friend_rail_id != rail::kInvalidRailId) { FUniqueNetIdRef FriendId(FUniqueNetIdRail::Create(RailFriendInfo.friend_rail_id)); ReportedPlayers.Emplace(FriendId, PresenceStr); } else { UE_LOG_ONLINE_FRIEND(Warning, TEXT("Invalid friend in friends list")); } } } } } FOnlineAsyncTaskRailReportPlayedWithUsers* NewTask = new FOnlineAsyncTaskRailReportPlayedWithUsers(this, ReportedPlayers, FOnOnlineAsyncTaskRailReportPlayedWithUsersComplete::CreateLambda([](const FReportPlayedWithUsersTaskResult& Result) { UE_LOG_ONLINE(Display, TEXT("Reporting players complete: %s"), *Result.Error.ToLogString()); })); QueueAsyncTask(NewTask); bWasHandled = true; } else if (FParse::Command(&Cmd, TEXT("DUMP"))) { // Retrieve local user presence if (TencentPresence.IsValid()) { TWeakPtr LocalWeakThis(AsShared()); IOnlinePresence::FOnPresenceTaskCompleteDelegate CompletionDelegate = IOnlinePresence::FOnPresenceTaskCompleteDelegate::CreateLambda([LocalWeakThis](const FUniqueNetId& UserId, const bool bWasSuccessful) { UE_LOG_ONLINE(Display, TEXT("Presence [%s]"), *UserId.ToDebugString()); FOnlineSubsystemTencentPtr StrongThis = StaticCastSharedPtr(LocalWeakThis.Pin()); if (StrongThis.IsValid()) { if (bWasSuccessful) { IOnlinePresencePtr PresenceInt = StrongThis->GetPresenceInterface(); if (PresenceInt.IsValid()) { TSharedPtr UserPresence; if (PresenceInt->GetCachedPresence(UserId, UserPresence) == EOnlineCachedResult::Success && UserPresence.IsValid()) { UE_LOG_ONLINE(Display, TEXT("- %s"), *UserPresence->ToDebugString()); } else { UE_LOG_ONLINE(Display, TEXT("Failed to get cached presence")); } } } else { UE_LOG_ONLINE(Display, TEXT("Failed to query presence")); } } }); // Query and dump friends presence if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { rail::RailArray Friends; rail::RailResult Result = RailFriends->GetFriendsList(&Friends); if (Result == rail::kSuccess) { TArray FriendIds; for (uint32 RailIdx = 0; RailIdx < Friends.size(); ++RailIdx) { if (Friends[RailIdx].friend_rail_id != rail::kInvalidRailId) { FUniqueNetIdRailRef FriendId = FUniqueNetIdRail::Create(Friends[RailIdx].friend_rail_id); TencentPresence->QueryPresence(*FriendId, CompletionDelegate); } } } } // Query own presence FUniqueNetIdPtr UserId = GetFirstSignedInUser(TencentIdentity); if (UserId.IsValid()) { TencentPresence->QueryPresence(*UserId, CompletionDelegate); } } bWasHandled = true; } #endif //WITH_TENCENT_RAIL_SDK return bWasHandled; } bool FOnlineSubsystemTencent::HandleUsersExecCommands(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { bool bWasHandled = false; #if WITH_TENCENT_RAIL_SDK const FUniqueNetIdPtr LocalUserId = GetFirstSignedInUser(TencentIdentity); const int32 LocalUserNum = LocalUserId.IsValid() ? TencentIdentity->GetPlatformUserIdFromUniqueNetId(*LocalUserId) : PLATFORMUSERID_NONE; if (LocalUserNum != (int32)PLATFORMUSERID_NONE) { bWasHandled = true; if (FParse::Command(&Cmd, TEXT("DUMPALL"))) { TArray> Users; if (TencentUser->GetAllUserInfo(LocalUserNum, Users)) { UE_LOG_ONLINE(Display, TEXT("%d users:"), Users.Num()); int32 Counter = 0; for (const TSharedRef& User : Users) { UE_LOG_ONLINE(Display, TEXT(" [%d]"), Counter++); UE_LOG_ONLINE(Display, TEXT(" id: %s"), *User->GetUserId()->ToDebugString()); UE_LOG_ONLINE(Display, TEXT(" display name: %s"), *User->GetDisplayName()); } } else { UE_LOG_ONLINE(Display, TEXT("GetAllUserInfo failed")); } } else if (FParse::Command(&Cmd, TEXT("QUERYUSER"))) { FString UserIdString = Cmd; if (!UserIdString.IsEmpty()) { FUniqueNetIdPtr UserId = TencentIdentity->CreateUniquePlayerId(UserIdString); if (UserId.IsValid()) { TArray UserIds; UserIds.Add(UserId.ToSharedRef()); static FDelegateHandle DelegateHandle1; FOnQueryUserInfoCompleteDelegate OnQueryUserInfoCompleteDelegate = FOnQueryUserInfoCompleteDelegate::CreateLambda([this, LocalUserNum, UserId](int32, bool bSucceeded, const TArray< FUniqueNetIdRef >&, const FString& ErrorString) { if (bSucceeded) { TSharedPtr OnlineUser = TencentUser->GetUserInfo(LocalUserNum, *UserId); if (OnlineUser.IsValid()) { UE_LOG_ONLINE(Display, TEXT("User %s found"), *UserId->ToDebugString()); UE_LOG_ONLINE(Display, TEXT(" Id: %s"), *OnlineUser->GetUserId()->ToDebugString()); UE_LOG_ONLINE(Display, TEXT(" Display name: %s"), *OnlineUser->GetDisplayName()); } else { UE_LOG_ONLINE(Display, TEXT("User %s not found"), *UserId->ToDebugString()); } } else { UE_LOG_ONLINE(Display, TEXT("Query failed with error %s"), *ErrorString); } TencentUser->ClearOnQueryUserInfoCompleteDelegate_Handle(LocalUserNum, DelegateHandle1); }); DelegateHandle1 = TencentUser->AddOnQueryUserInfoCompleteDelegate_Handle(LocalUserNum, OnQueryUserInfoCompleteDelegate); TencentUser->QueryUserInfo(LocalUserNum, UserIds); } else { UE_LOG_ONLINE(Display, TEXT("Failed to create rail id from '%s'"), *UserIdString); } } else { UE_LOG_ONLINE(Display, TEXT("Missing UserId")); } } else if (FParse::Command(&Cmd, TEXT("QUERYALLFRIENDS"))) { // Just a test to see what we get when we query all our friends rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends(); if (RailFriends) { rail::RailArray Friends; rail::RailResult Result = RailFriends->GetFriendsList(&Friends); if (Result == rail::kSuccess) { TArray UserIds; for (uint32 Index = 0; Index < Friends.size(); ++Index) { UserIds.Emplace(FUniqueNetIdRail::Create(Friends[Index].friend_rail_id)); } static FDelegateHandle DelegateHandle2; FOnQueryUserInfoCompleteDelegate OnQueryUserInfoCompleteDelegate = FOnQueryUserInfoCompleteDelegate::CreateLambda([this, LocalUserNum, UserIds](int32, bool bSucceeded, const TArray< FUniqueNetIdRef >&, const FString& ErrorString) { if (bSucceeded) { for (const FUniqueNetIdRef& UserId : UserIds) { TSharedPtr OnlineUser = TencentUser->GetUserInfo(LocalUserNum, *UserId); if (OnlineUser.IsValid()) { UE_LOG_ONLINE(Display, TEXT("User %s found"), *UserId->ToDebugString()); UE_LOG_ONLINE(Display, TEXT(" Id: %s"), *OnlineUser->GetUserId()->ToDebugString()); UE_LOG_ONLINE(Display, TEXT(" Display name: %s"), *OnlineUser->GetDisplayName()); } else { UE_LOG_ONLINE(Display, TEXT("User %s not found"), *UserId->ToDebugString()); } } } else { UE_LOG_ONLINE(Display, TEXT("Query failed with error %s"), *ErrorString); } TencentUser->ClearOnQueryUserInfoCompleteDelegate_Handle(LocalUserNum, DelegateHandle2); }); DelegateHandle2 = TencentUser->AddOnQueryUserInfoCompleteDelegate_Handle(LocalUserNum, OnQueryUserInfoCompleteDelegate); TencentUser->QueryUserInfo(LocalUserNum, UserIds); } else { UE_LOG_ONLINE(Display, TEXT("GetFriendsList failed %s"), *LexToString(Result)); } } else { UE_LOG_ONLINE(Display, TEXT("Failed to get RailFriends")); } } else { bWasHandled = false; } } else { UE_LOG_ONLINE(Display, TEXT("You must be logged in first")); } #endif //WITH_TENCENT_RAIL_SDK return bWasHandled; } bool FOnlineSubsystemTencent::HandleFriendExecCommands(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { bool bWasHandled = false; #if WITH_TENCENT_RAIL_SDK if (FParse::Command(&Cmd, TEXT("DUMPFRIENDS"))) { if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { rail::RailArray Friends; rail::RailResult Result = RailFriends->GetFriendsList(&Friends); TArray FriendIds; for (uint32 RailIdx = 0; RailIdx < Friends.size(); ++RailIdx) { if (Friends[RailIdx].friend_rail_id != rail::kInvalidRailId) { FriendIds.Add(FUniqueNetIdRail::Create(Friends[RailIdx].friend_rail_id)); } } if (FriendIds.Num() > 0) { TencentUser->AddOnQueryUserInfoCompleteDelegate_Handle(0, FOnQueryUserInfoCompleteDelegate::CreateLambda([this](int32 LocalUserNum, bool bWasSuccessful, const TArray& UserIds, const FString& ErrorStr) { if (bWasSuccessful) { TArray> OutUsers; this->TencentUser->GetAllUserInfo(0, OutUsers); for (const TSharedRef& User : OutUsers) { UE_LOG_ONLINE(Display, TEXT(" Id: %s"), *User->GetUserId()->ToDebugString()); UE_LOG_ONLINE(Display, TEXT(" Display name: %s"), *User->GetDisplayName()); } } else { UE_LOG_ONLINE(Display, TEXT("Query failed with error %s"), *ErrorStr); } })); TencentUser->QueryUserInfo(0, FriendIds); } } bWasHandled = true; } else if (FParse::Command(&Cmd, TEXT("DUMPPLAYEDGAMES"))) { if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { rail::RailArray Friends; rail::RailResult Result = RailFriends->GetFriendsList(&Friends); FUniqueNetIdRailPtr LocalUserId = StaticCastSharedPtr(GetFirstSignedInUser(TencentIdentity)); if (LocalUserId.IsValid()) { for (uint32 RailIdx = 0; RailIdx < Friends.size(); ++RailIdx) { if (Friends[RailIdx].friend_rail_id != rail::kInvalidRailId) { if (Friends[RailIdx].friend_rail_id != (rail::RailID)(*LocalUserId)) { UE_LOG_ONLINE(Display, TEXT("Query game played with %llu"), Friends[RailIdx].friend_rail_id.get_id()); FUniqueNetIdRailPtr FriendId = FUniqueNetIdRail::Create(Friends[RailIdx].friend_rail_id); FOnlineAsyncTaskRailQueryFriendPlayedGamesInfo* NewTask = new FOnlineAsyncTaskRailQueryFriendPlayedGamesInfo(this, *FriendId, FOnOnlineAsyncTaskRailQueryFriendPlayedGamesComplete::CreateLambda([](const FQueryFriendPlayedGamesTaskResult& Result) { UE_LOG_ONLINE(Display, TEXT("Query game played complete: %s"), *Result.Error.ToLogString()); })); QueueAsyncTask(NewTask); } } } } } bWasHandled = true; } #endif return bWasHandled; } bool FOnlineSubsystemTencent::HandleRailSdkWrapperExecCommands(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { #if WITH_TENCENT_RAIL_SDK if (FParse::Command(&Cmd, TEXT("LOAD"))) { const bool bResult = RailSdkWrapper::Get().Load(); UE_LOG_ONLINE(Display, TEXT("RailSdkWrapper Load: %d"), static_cast(bResult)); } else if (FParse::Command(&Cmd, TEXT("SHUTDOWN"))) { RailSdkWrapper::Get().Shutdown(); UE_LOG_ONLINE(Display, TEXT("RailSdkWrapper Shutdown")); } else if (FParse::Command(&Cmd, TEXT("NEEDRESTART"))) { const bool bResult = RailSdkWrapper::Get().RailNeedRestartAppForCheckingEnvironment(rail::RailGameID(RailGameId)); static const TCHAR* NeedsRestartString = TEXT("true (needs restart)"); static const TCHAR* DoesNotNeedRestartString = TEXT("false (does not need restart)"); const TCHAR* LogString = bResult ? NeedsRestartString : DoesNotNeedRestartString; UE_LOG_ONLINE(Display, TEXT("RailNeedRestartAppForCheckingEnvironment: %s for game id %llu"), LogString, RailGameId); } else if (FParse::Command(&Cmd, TEXT("INITIALIZE"))) { const bool bResult = RailSdkWrapper::Get().RailInitialize(); UE_LOG_ONLINE(Display, TEXT("RailInitialize: %d"), static_cast(bResult)); } else if (FParse::Command(&Cmd, TEXT("PLAYER"))) { return HandleRailSdkWrapperPlayerExecCommands(InWorld, Cmd, Ar); } else if (FParse::Command(&Cmd, TEXT("VERSION"))) { rail::IRailGame* const RailGame = RailSdkWrapper::Get().RailGame(); if (LIKELY(RailGame != nullptr)) { rail::RailString BranchBuildNumber; rail::RailResult BranchBuildNumberResult = RailGame->GetBranchBuildNumber(&BranchBuildNumber); if (BranchBuildNumberResult == rail::RailResult::kSuccess) { UE_LOG_ONLINE(Log, TEXT("BranchBuildNumber: %s"), *LexToString(BranchBuildNumber)); } else { UE_LOG_ONLINE(Log, TEXT("BranchBuildNumber error! %s"), *LexToString(BranchBuildNumberResult)); } rail::RailBranchInfo BranchInfo; rail::RailResult BranchInfoResult = RailGame->GetCurrentBranchInfo(&BranchInfo); if (BranchInfoResult == rail::RailResult::kSuccess) { UE_LOG_ONLINE(Log, TEXT("GetCurrentBranchInfo: branch_name=%s"), *LexToString(BranchInfo.branch_name)); UE_LOG_ONLINE(Log, TEXT("GetCurrentBranchInfo: branch_type=%s"), *LexToString(BranchInfo.branch_type)); UE_LOG_ONLINE(Log, TEXT("GetCurrentBranchInfo: branch_id=%s"), *LexToString(BranchInfo.branch_id)); UE_LOG_ONLINE(Log, TEXT("GetCurrentBranchInfo: build_number=%s"), *LexToString(BranchInfo.build_number)); } else { UE_LOG_ONLINE(Log, TEXT("GetCurrentBranchInfo error! %s"), *LexToString(BranchInfoResult)); } } } else { return false; } return true; #else return false; #endif } bool FOnlineSubsystemTencent::HandleRailSdkWrapperPlayerExecCommands(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { #if WITH_TENCENT_RAIL_SDK rail::IRailFactory* Factory = RailSdkWrapper::Get().RailFactory(); if (Factory) { rail::IRailPlayer* Player = Factory->RailPlayer(); if (Player) { if (FParse::Command(&Cmd, TEXT("INFO"))) { const rail::RailID RailId = Player->GetRailID(); rail::RailString RailPlayerName; rail::RailResult RailPlayerNameResult = Player->GetPlayerName(&RailPlayerName); const FString PlayerName = LexToString(RailPlayerName); const bool bAlreadyLoggedIn = Player->AlreadyLoggedIn(); static const TCHAR* LoggedInString = TEXT("true (logged in)"); static const TCHAR* NotLoggedInString = TEXT("false (not logged in)"); const TCHAR* LoggedInLogString = bAlreadyLoggedIn ? LoggedInString : NotLoggedInString; rail::RailString RailDataPath; rail::RailResult RailDataPathResult = Player->GetPlayerDataPath(&RailDataPath); FString DataPath = LexToString(RailDataPath); const rail::EnumRailPlayerOwnershipType OwnershipType = Player->GetPlayerOwnershipType(); UE_LOG_ONLINE(Display, TEXT("ID: %llu (domain %u)"), RailId.get_id(), static_cast(RailId.GetDomain())); UE_LOG_ONLINE(Display, TEXT("Name: '%s' (result %s)"), *PlayerName, *LexToString(RailPlayerNameResult)); UE_LOG_ONLINE(Display, TEXT("Logged in: %s"), LoggedInLogString); UE_LOG_ONLINE(Display, TEXT("DataPath: '%s' (result %s)"), *DataPath, *LexToString(RailDataPathResult)); UE_LOG_ONLINE(Display, TEXT("OwnershipType: %u"), static_cast(OwnershipType)); } else if (FParse::Command(&Cmd, TEXT("ACQUIRESESSIONTICKET"))) { const rail::RailID RailId = Player->GetRailID(); FOnlineAsyncTaskRailAcquireSessionTicket::FCompletionDelegate CompletionDelegate; CompletionDelegate.BindLambda([](const FOnlineError& OnlineError, const FString& SessionTicket) { if (OnlineError.WasSuccessful()) { UE_LOG_ONLINE(Display, TEXT("SessionTicket: '%s'"), *SessionTicket); } else { UE_LOG_ONLINE(Display, TEXT("SessionTicket error: '%s'"), *OnlineError.ToLogString()); } }); QueueAsyncTask(new FOnlineAsyncTaskRailAcquireSessionTicket(this, RailId, CompletionDelegate)); } else { return false; } } else { UE_LOG_ONLINE(Display, TEXT("Unable to get Player")); } } else { UE_LOG_ONLINE(Display, TEXT("Unable to get Factory")); } return true; #else return false; #endif } IMessageSanitizerPtr FOnlineSubsystemTencent::GetMessageSanitizer(int32 LocalUserNum, FString& OutAuthTypeToExclude) const { #if WITH_TENCENT_RAIL_SDK return TencentMessageSanitizer; #else return nullptr; #endif } FOnlineSubsystemTencent::FOnlineSubsystemTencent(FName InInstanceName) : FOnlineSubsystemImpl(TENCENT_SUBSYSTEM, InInstanceName) , OnlineAsyncTaskThreadRunnable(nullptr) , OnlineAsyncTaskThread(nullptr) { } FOnlineSubsystemTencent::~FOnlineSubsystemTencent() { } #endif // WITH_TENCENTSDK