diff --git a/External/Xal/Source/Xal/Include/Xal/xal_version.h b/External/Xal/Source/Xal/Include/Xal/xal_version.h index 5216e3dd..4b6ba39e 100644 --- a/External/Xal/Source/Xal/Include/Xal/xal_version.h +++ b/External/Xal/Source/Xal/Include/Xal/xal_version.h @@ -20,6 +20,6 @@ extern "C" /// YYYYMMDD Date string describing the date the build was created /// rrr QFE number (000 indicates base release) /// -#define XAL_VERSION "2020.11.20201204.001" +#define XAL_VERSION "2021.04.20210319.000" } diff --git a/Include/xsapi-c/multiplayer_c.h b/Include/xsapi-c/multiplayer_c.h index a298a615..7527f701 100644 --- a/Include/xsapi-c/multiplayer_c.h +++ b/Include/xsapi-c/multiplayer_c.h @@ -3293,9 +3293,13 @@ STDAPI XblMultiplayerGetSessionAsync( /// Gets the result of an XblMultiplayerGetSessionResult call. /// /// The AsyncBlock for this operation. -/// Passes back a handle to a new instance of a local multiplayer session object. +/// Passes back a handle to a new instance of a local multiplayer session object. /// It must be release by the caller with . /// HRESULT return code for this API operation. +/// +/// If the session does not exist, this API will return __HRESULT_FROM_WIN32(ERROR_RESOURCE_DATA_NOT_FOUND) and a null +/// XblMultiplayerSessionHandle. +/// STDAPI XblMultiplayerGetSessionResult( _In_ XAsyncBlock* async, _Out_ XblMultiplayerSessionHandle* handle @@ -3326,6 +3330,10 @@ STDAPI XblMultiplayerGetSessionByHandleAsync( /// Passes back a handle to a new instance of a local multiplayer session object. /// It must be release by the caller with . /// HRESULT return code for this API operation. +/// +/// If the session does not exist, this API will return __HRESULT_FROM_WIN32(ERROR_RESOURCE_DATA_NOT_FOUND) and a null +/// XblMultiplayerSessionHandle. +/// STDAPI XblMultiplayerGetSessionByHandleResult( _In_ XAsyncBlock* async, _Out_ XblMultiplayerSessionHandle* handle diff --git a/Include/xsapi-c/multiplayer_manager_c.h b/Include/xsapi-c/multiplayer_manager_c.h index 80621dd2..300725d1 100644 --- a/Include/xsapi-c/multiplayer_manager_c.h +++ b/Include/xsapi-c/multiplayer_manager_c.h @@ -754,6 +754,7 @@ XBL_WARNING_POP /// Changes are batched and written to the service on the next XblMultiplayerManagerDoWork(). /// All session properties and members contain updated response returned from the server /// upon calling XblMultiplayerManagerDoWork(). +/// When attempting to join a session, the service will return HTTP_E_STATUS_BAD_REQUEST (HTTP status 400) in the event the server is full. /// STDAPI XblMultiplayerManagerLobbySessionAddLocalUser( _In_ XblUserHandle user @@ -1112,6 +1113,7 @@ STDAPI XblMultiplayerManagerGameSessionSetSynchronizedHost( /// After joining, you can set the host via XblMultiplayerManagerLobbySessionSetSynchronizedHost() if one doesn't exist. /// Instead, if you don't need a lobby session, and if you haven't added the local users through /// XblMultiplayerManagerLobbySessionAddLocalUser(), you can pass in the list of users via the XblMultiplayerManagerJoinGame() API. +/// When attempting to join a session, the service will return HTTP_E_STATUS_BAD_REQUEST (HTTP status 400) in the event the server is full. /// STDAPI XblMultiplayerManagerJoinLobby( _In_z_ const char* handleId, @@ -1129,6 +1131,7 @@ STDAPI XblMultiplayerManagerJoinLobby( /// through XblMultiplayerManagerDoWork(). /// This does not migrate existing lobby session properties over to the game session. /// After joining, you can set the properties or the host via XblMultiplayerManagerGameSessionSetSynchronized APIs. +/// When attempting to join a session, the service will return HTTP_E_STATUS_BAD_REQUEST (HTTP status 400) in the event the server is full. /// STDAPI XblMultiplayerManagerJoinGameFromLobby( _In_z_ const char* sessionTemplateName @@ -1147,6 +1150,7 @@ STDAPI XblMultiplayerManagerJoinGameFromLobby( /// /// The result is delivered via an event of type XblMultiplayerEventType::JoinGameCompleted through XblMultiplayerManagerDoWork(). /// After joining, you can set the properties or the host via XblMultiplayerManagerGameSessionSetSynchronized APIs. +/// When attempting to join a session, the service will return HTTP_E_STATUS_BAD_REQUEST (HTTP status 400) in the event the server is full. /// STDAPI XblMultiplayerManagerJoinGame( _In_z_ const char* sessionName, diff --git a/Include/xsapi-c/social_manager_c.h b/Include/xsapi-c/social_manager_c.h index 32da9fe8..25a84e5b 100644 --- a/Include/xsapi-c/social_manager_c.h +++ b/Include/xsapi-c/social_manager_c.h @@ -63,11 +63,17 @@ enum class XblPresenceFilter : uint32_t /// /// Has played this title and is offline. /// + /// + /// This filter option requires ::TitleHistoryLevel to be set in + /// TitleOffline, /// /// Has played this title, is online but not currently playing this title. /// + /// + /// This filter option requires ::TitleHistoryLevel to be set in + /// TitleOnlineOutsideTitle, /// @@ -83,6 +89,9 @@ enum class XblPresenceFilter : uint32_t /// /// Everyone who has played or is playing the title. /// + /// + /// This filter option requires ::TitleHistoryLevel to be set in + /// AllTitle, /// diff --git a/Source/Services/Achievements/Manager/achievements_manager_internal.cpp b/Source/Services/Achievements/Manager/achievements_manager_internal.cpp index 359fac79..9eda12cc 100644 --- a/Source/Services/Achievements/Manager/achievements_manager_internal.cpp +++ b/Source/Services/Achievements/Manager/achievements_manager_internal.cpp @@ -10,9 +10,9 @@ using namespace xbox::services; XblAchievementsManagerResult::XblAchievementsManagerResult(_In_ const XblAchievement & achievement) : m_achievements({ achievement }), - m_explicitCleanup(false), m_achievementsData(nullptr), - m_achievementsCount() + m_achievementsCount(), + m_explicitCleanup(false) { } @@ -63,9 +63,9 @@ AchievementsManagerUser::AchievementsManagerUser( _In_ User&& localUser, _In_ const TaskQueue& queue ) noexcept : - m_queue{ queue.DeriveWorkerQueue() }, m_xuid{ localUser.Xuid() }, - m_rtaManager{ GlobalState::Get()->RTAManager() } + m_rtaManager{ GlobalState::Get()->RTAManager() }, + m_queue{ queue.DeriveWorkerQueue() } { // Maintain legacy RTA activation count. m_rtaManager->Activate(localUser); diff --git a/Source/Services/Achievements/achievement_service_internal.cpp b/Source/Services/Achievements/achievement_service_internal.cpp index 265ae2c9..85993fcc 100644 --- a/Source/Services/Achievements/achievement_service_internal.cpp +++ b/Source/Services/Achievements/achievement_service_internal.cpp @@ -74,10 +74,10 @@ AchievementsService::AchievementsService( _In_ std::shared_ptr rtaManager ) : m_user{ std::move(user) }, - m_xboxLiveContextSettings(std::move(xboxLiveContextSettings)), - m_appConfig(std::move(appConfig)), - m_xboxLiveContextImpl(std::move(xboxLiveContextImpl)), - m_rtaManager{ std::move(rtaManager) } + m_xboxLiveContextSettings{ std::move(xboxLiveContextSettings) }, + m_appConfig{ std::move(appConfig) }, + m_rtaManager{ std::move(rtaManager) }, + m_xboxLiveContextImpl{ std::move(xboxLiveContextImpl) } { } diff --git a/Source/Services/Common/xbox_live_context.cpp b/Source/Services/Common/xbox_live_context.cpp index b5c03b29..31b262fe 100644 --- a/Source/Services/Common/xbox_live_context.cpp +++ b/Source/Services/Common/xbox_live_context.cpp @@ -167,8 +167,8 @@ HRESULT XblContext::Initialize( RETURN_HR_IF_FAILED(userResult.Hresult()); #if XSAPI_NOTIFICATION_SERVICE #if !XSAPI_UNIT_TESTS && (HC_PLATFORM == HC_PLATFORM_WIN32 || HC_PLATFORM_IS_EXTERNAL) - m_notificationService = MakeShared(userResult.ExtractPayload(), m_xboxLiveContextSettings, rtaManager); - m_notificationService->RegisterForSpopNotificationEvents(); + m_notificationService = MakeShared(userResult.ExtractPayload(), globalQueue, m_xboxLiveContextSettings, rtaManager); + RETURN_HR_IF_FAILED(m_notificationService->Initialize()); #elif HC_PLATFORM == HC_PLATFORM_ANDROID || HC_PLATFORM == HC_PLATFORM_IOS m_notificationService = MakeShared(userResult.ExtractPayload(), m_xboxLiveContextSettings); #elif HC_PLATFORM == HC_PLATFORM_UWP diff --git a/Source/Services/Common/xbox_live_context_internal.h b/Source/Services/Common/xbox_live_context_internal.h index 01522a49..4594cdaa 100644 --- a/Source/Services/Common/xbox_live_context_internal.h +++ b/Source/Services/Common/xbox_live_context_internal.h @@ -169,7 +169,7 @@ private: std::shared_ptr m_eventsService; #endif - XblFunctionContext m_userChangeEventToken{ 0 }; + uint64_t m_userChangeEventToken{ 0 }; uint64_t m_xuid{ 0 }; xbox::services::User m_user; diff --git a/Source/Services/Leaderboard/leaderboard_result.cpp b/Source/Services/Leaderboard/leaderboard_result.cpp index f2c8c98d..719dd0ea 100644 --- a/Source/Services/Leaderboard/leaderboard_result.cpp +++ b/Source/Services/Leaderboard/leaderboard_result.cpp @@ -68,36 +68,39 @@ LeaderboardResult::ParseAdditionalColumns(const xsapi_internal_vectorsecond == legacy::leaderboard_stat_type::stat_other) + if (row.m_metadata.IsObject() && row.m_metadata.HasMember(columnName.data())) { - if(val.IsBool()) + const JsonValue& val = row.m_metadata[columnName.c_str()]; + if (stat == stats.end() || stat->second == legacy::leaderboard_stat_type::stat_other) { - stats[columnName] = legacy::leaderboard_stat_type::stat_boolean; + if (val.IsBool()) + { + stats[columnName] = legacy::leaderboard_stat_type::stat_boolean; + } + else if (val.IsNumber()) + { + stats[columnName] = legacy::leaderboard_stat_type::stat_double; + } + else if (val.IsString()) + { + stats[columnName] = legacy::leaderboard_stat_type::stat_string; + } + else + { + stats[columnName] = legacy::leaderboard_stat_type::stat_other; + } + } - else if (val.IsNumber()) + + auto columnValues = JsonUtils::SerializeJson(val); + if (i >= row.m_columnValues.size() - 1) { - stats[columnName] = legacy::leaderboard_stat_type::stat_double; - } - else if (val.IsString()) - { - stats[columnName] = legacy::leaderboard_stat_type::stat_string; + row.m_columnValues.push_back(columnValues); } else { - stats[columnName] = legacy::leaderboard_stat_type::stat_other; + row.m_columnValues[i] = columnValues; } - - } - - auto columnValues = JsonUtils::SerializeJson(val); - if (i >= row.m_columnValues.size() - 1) - { - row.m_columnValues.push_back(columnValues); - } - else - { - row.m_columnValues[i] = columnValues; } } } diff --git a/Source/Services/Multiplayer/multiplayer_service.cpp b/Source/Services/Multiplayer/multiplayer_service.cpp index d9d285a8..6791bd1d 100644 --- a/Source/Services/Multiplayer/multiplayer_service.cpp +++ b/Source/Services/Multiplayer/multiplayer_service.cpp @@ -2025,7 +2025,7 @@ STDAPI XblMultiplayerGetActivitiesWithPropertiesForSocialGroupResult( size_t count{ 0 }; size_t verifiedSize{ 0 }; - for (; verifiedSize < *bufferUsed - XBL_ALIGN_SIZE; ++count) + for (; *bufferUsed > 0 && verifiedSize < *bufferUsed - XBL_ALIGN_SIZE; ++count) { verifiedSize += sizeof(XblMultiplayerActivityDetails); verifiedSize += strlen((*ptrToBuffer)[count].CustomSessionPropertiesJson) + 1; diff --git a/Source/Services/MultiplayerActivity/multiplayer_activity_api.cpp b/Source/Services/MultiplayerActivity/multiplayer_activity_api.cpp index d8b48fc5..8ad6906b 100644 --- a/Source/Services/MultiplayerActivity/multiplayer_activity_api.cpp +++ b/Source/Services/MultiplayerActivity/multiplayer_activity_api.cpp @@ -291,7 +291,7 @@ STDAPI_(XblFunctionContext) XblMultiplayerActivityAddInviteHandler( } auto rtaNotificationService = std::dynamic_pointer_cast(xblContext->NotificationService()); - return rtaNotificationService->AddGameInviteHandler(GameInviteSubscription::MultiplayerActivityInviteHandler{ + return rtaNotificationService->AddGameInviteHandler(NotificationSubscription::MultiplayerActivityInviteHandler{ [ handler, context ] @@ -316,7 +316,7 @@ STDAPI XblMultiplayerActivityRemoveInviteHandler( { RETURN_HR_INVALIDARGUMENT_IF_NULL(xblContext); auto rtaNotificationService = std::dynamic_pointer_cast(xblContext->NotificationService()); - rtaNotificationService->RemoveGameInviteHandler(token); + rtaNotificationService->RemoveNotificationHandler(token); return S_OK; } #endif diff --git a/Source/Services/Notification/RTA/achievement_unlock_subscription.cpp b/Source/Services/Notification/RTA/achievement_unlock_subscription.cpp deleted file mode 100644 index d03b153d..00000000 --- a/Source/Services/Notification/RTA/achievement_unlock_subscription.cpp +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) Microsoft Corporation -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#include "pch.h" -#include "achievement_unlock_subscription.h" - - -#if HC_PLATFORM == HC_PLATFORM_WIN32 || HC_PLATFORM_IS_EXTERNAL - -NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_BEGIN - -AchievementUnlockSubscription::AchievementUnlockSubscription(uint64_t xuid, uint32_t titleId) noexcept -{ - Stringstream ss; - ss << "https://notify.xboxlive.com/users/xuid(" << xuid << ")/deviceId/current/titleId/" << titleId; - m_resourceUri = ss.str(); -} - -const String & -AchievementUnlockSubscription::ResourceUri() const noexcept -{ - return m_resourceUri; -} - -XblFunctionContext -AchievementUnlockSubscription::AddHandler(EventHandler handler) noexcept -{ - std::lock_guard lock{ m_mutex }; - m_handlers[m_nextId] = std::move(handler); - return m_nextId++; -} - -size_t -AchievementUnlockSubscription::RemoveHandler(XblFunctionContext id) noexcept -{ - std::lock_guard lock{ m_mutex }; - m_handlers.erase(id); - return m_handlers.size(); -} - - -void -AchievementUnlockSubscription::OnEvent(_In_ const JsonValue& data) noexcept -{ - String originatingService; - JsonUtils::ExtractJsonString(data, "service", originatingService); - if (!data.IsObject() || originatingService != "achievements") - { - return; - } - - // Deserialize and send invite notification - auto deserializationResult = AchievementUnlockEvent::Deserialize(data); - if (Succeeded(deserializationResult)) - { - std::unique_lock lock{ m_mutex }; - auto handlers{ m_handlers }; - lock.unlock(); - - for (auto& handler : handlers) - { - handler.second(deserializationResult.Payload()); - } - } -} - -void -AchievementUnlockSubscription::OnResync() noexcept -{ - LOGS_ERROR << __FUNCTION__ << ": Achievement Unlock event may have been discarded by RTA service"; -} - -AchievementUnlockEvent::AchievementUnlockEvent( AchievementUnlockEvent&& event ) noexcept: - m_achievementId(std::move(event.m_achievementId)), - m_achievementName(std::move(event.m_achievementName)), - m_achievementDescription(std::move(event.m_achievementDescription)), - m_achievementIconUri(std::move(event.m_achievementIconUri)), - m_deepLink(event.m_deepLink) -{ - // strings for the C API - achievementId = m_achievementId.c_str(); - achievementName = m_achievementName.c_str(); - achievementDescription = m_achievementDescription.c_str(); - achievementIcon = m_achievementIconUri.c_str(); - titleId = event.titleId; - gamerscore = event.gamerscore; - rarityPercentage = event.rarityPercentage; - rarityCategory = event.rarityCategory; - timeUnlocked = event.timeUnlocked; - xboxUserId = event.xboxUserId; -} - -AchievementUnlockEvent::AchievementUnlockEvent( const AchievementUnlockEvent& event ) : - m_achievementId(event.m_achievementId), - m_achievementName(event.m_achievementName), - m_achievementDescription(event.m_achievementDescription), - m_achievementIconUri(event.m_achievementIconUri), - m_deepLink(event.m_deepLink) -{ - // strings for the C API - achievementId = m_achievementId.c_str(); - achievementName = m_achievementName.c_str(); - achievementDescription = m_achievementDescription.c_str(); - achievementIcon = m_achievementIconUri.c_str(); - - titleId = event.titleId; - gamerscore = event.gamerscore; - rarityPercentage = event.rarityPercentage; - rarityCategory = event.rarityCategory; - timeUnlocked = event.timeUnlocked; - xboxUserId = event.xboxUserId; -} - - -Result AchievementUnlockEvent::Deserialize( _In_ const JsonValue& json ) noexcept -{ - AchievementUnlockEvent result{}; - if (!json.IsObject()) - { - return result; - } - - double rarityPercentage = 0.0; // We are guaranteed that rarityPercentage is only float, but can only extract as double. - uint64_t titleId = 0; // We are guaranteed that titleId is only uint32, but can only extract as uint64. - String rarityCategory; // Will be converted into an enum value after extraction - - RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonString(json, "achievementId", result.m_achievementId, true)); - - RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonString(json, "achievementDescription", result.m_achievementDescription, false)); - - RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonString(json, "achievementName", result.m_achievementName, true)); - - RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonString(json, "achievementIcon", result.m_achievementIconUri, true)); - - RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonStringToUInt64( json, "titleId", titleId, true)); - - RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonUInt64( json, "gamerscore", result.gamerscore, true)); - - RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonXuid(json, "xuid", result.xboxUserId, true)); - - RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonString(json, "extendedInfoUrl", result.m_deepLink, true)); - - // RarityPercentage and rarityCategory are only in payload version 2 so make them optional - RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonDouble(json, "rarityPercentage", rarityPercentage, false)); - - RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonString(json, "rarityCategory", rarityCategory, false)); - - RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonTimeT(json, "unlockTime", result.timeUnlocked)); - - result.titleId = static_cast(titleId); - result.rarityPercentage = static_cast(rarityPercentage); - result.rarityCategory = EnumValue(rarityCategory.c_str()); - - // strings for the C API - result.achievementId = result.m_achievementId.c_str(); - result.achievementName = result.m_achievementName.c_str(); - if (!result.m_achievementDescription.empty()) - { - result.achievementDescription = result.m_achievementDescription.c_str(); - } - result.achievementIcon = result.m_achievementIconUri.c_str(); - result.m_deepLink = result.m_deepLink.c_str(); - - return result; -} - -NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_END - -#endif \ No newline at end of file diff --git a/Source/Services/Notification/RTA/achievement_unlock_subscription.h b/Source/Services/Notification/RTA/achievement_unlock_subscription.h deleted file mode 100644 index 6cb8da65..00000000 --- a/Source/Services/Notification/RTA/achievement_unlock_subscription.h +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#pragma once -#include "xsapi-c/achievements_c.h" -#include "real_time_activity_subscription.h" - -#if HC_PLATFORM == HC_PLATFORM_WIN32 || HC_PLATFORM_IS_EXTERNAL - -NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_BEGIN - - struct AchievementUnlockEvent : public XblAchievementUnlockEvent - { - public: - - AchievementUnlockEvent() = default; - AchievementUnlockEvent( AchievementUnlockEvent&& event) noexcept; - AchievementUnlockEvent(const AchievementUnlockEvent& event); - - static Result Deserialize(_In_ const JsonValue& json ) noexcept; - - private: - - String m_achievementId; - String m_achievementDescription; - String m_achievementName; - String m_achievementIconUri; - String m_deepLink; - }; - - - class AchievementUnlockSubscription : public real_time_activity::Subscription - { - public: - - AchievementUnlockSubscription(uint64_t xuid, uint32_t titleId) noexcept; - - using EventHandler = Function< void(const AchievementUnlockEvent&) >; - XblFunctionContext AddHandler(EventHandler handler) noexcept; - size_t RemoveHandler(XblFunctionContext id) noexcept; - const String & ResourceUri() const noexcept; - - - protected: - - void OnEvent(_In_ const JsonValue& data) noexcept override; - void OnResync() noexcept override; - - private: - - Map< XblFunctionContext, EventHandler> m_handlers; - XblFunctionContext m_nextId{ 0 }; - - std::mutex m_mutex; - }; - -NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_END - -#endif \ No newline at end of file diff --git a/Source/Services/Notification/RTA/notification_service_rta.cpp b/Source/Services/Notification/RTA/notification_service_rta.cpp index b85a24ab..76f41de9 100644 --- a/Source/Services/Notification/RTA/notification_service_rta.cpp +++ b/Source/Services/Notification/RTA/notification_service_rta.cpp @@ -11,112 +11,52 @@ NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_BEGIN -template -HRESULT RTANotificationService::RegisterForEvents(std::shared_ptr& subscription, - AsyncContext async) noexcept +RTANotificationService::RTANotificationService( + _In_ User&& user, + _In_ const TaskQueue& queue, + _In_ std::shared_ptr contextSettings, + _In_ std::shared_ptr rtaManager +) noexcept : + NotificationService(std::move(user), contextSettings), + m_taskQueue{ queue.DeriveWorkerQueue() }, + m_rtaManager{ std::move(rtaManager) } { +} + +RTANotificationService::~RTANotificationService() noexcept +{ + m_rtaManager->RemoveSubscription(m_user, m_rtaSubscription); + m_rtaManager->Deactivate(m_user); +} + +HRESULT RTANotificationService::Initialize() noexcept +{ + // Always register with notification service as it powers SPOP for Win32 + // Subscribing to events requires two separate steps: // 1) Creating and adding an RTA subscription to the notification endpoint // 2) Registering with the notification service std::lock_guard lock{ m_mutex }; - if (!subscription) - { - m_rtaManager->Activate(m_user); - subscription = MakeShared(m_user.Xuid(), AppConfig::Instance()->TitleId()); - RETURN_HR_IF_FAILED(m_rtaManager->AddSubscription(m_user, subscription)); - } + + auto copyUserResult = m_user.Copy(); + RETURN_HR_IF_FAILED(copyUserResult.Hresult()); + + m_rtaManager->Activate(m_user); + m_rtaSubscription = MakeShared(copyUserResult.ExtractPayload(), m_taskQueue, AppConfig::Instance()->TitleId()); + RETURN_HR_IF_FAILED(m_rtaManager->AddSubscription(m_user, m_rtaSubscription)); return RegisterWithNotificationService( - subscription->ResourceUri(), - std::move(async)); -}; - - -template -HRESULT RTANotificationService::RTANotificationService::UnregisterForEvents(std::shared_ptr& subscription, - AsyncContext async) noexcept -{ - std::lock_guard lock{ m_mutex }; - if (subscription) - { - m_rtaManager->RemoveSubscription(m_user, subscription); - subscription.reset(); - m_rtaManager->Deactivate(m_user); - } - - if (!m_gameInviteSubscription && !m_achievementUnlockSubscription && !m_spopNotificationSubscription) - { - return UnregisterFromNotificationService( - std::move(async)); - } - - return S_OK; -} - -RTANotificationService::RTANotificationService( - _In_ User&& user, - _In_ std::shared_ptr contextSettings, - _In_ std::shared_ptr rtaManager -) noexcept : - NotificationService(std::move(user), contextSettings), - m_rtaManager{ std::move(rtaManager) } -{ -} - -HRESULT RTANotificationService::RegisterForSpopNotificationEvents() noexcept -{ - std::lock_guard lock{ m_mutex }; - - auto copiedUserResult = m_user.Copy(); - RETURN_HR_IF_FAILED(copiedUserResult.Hresult()); - m_rtaManager->Activate(copiedUserResult.ExtractPayload()); - - { - auto subscriptionUserCopyResult = m_user.Copy(); - RETURN_HR_IF_FAILED(subscriptionUserCopyResult.Hresult()); - m_spopNotificationSubscription = MakeShared(subscriptionUserCopyResult.ExtractPayload(), AppConfig::Instance()->TitleId()); - } - - { - auto subscriptionUserCopyResult = m_user.Copy(); - RETURN_HR_IF_FAILED(subscriptionUserCopyResult.Hresult()); - RETURN_HR_IF_FAILED(m_rtaManager->AddSubscription(subscriptionUserCopyResult.ExtractPayload(), m_spopNotificationSubscription)); - } - - return RegisterWithNotificationService( - m_spopNotificationSubscription->ResourceUri(), - {}); -} - -HRESULT RTANotificationService::RegisterForGameInviteEvents( - _In_ AsyncContext async -) noexcept -{ - return RegisterForEvents(m_gameInviteSubscription, async); -}; - -HRESULT RTANotificationService::UnregisterForGameInviteEvents( - _In_ AsyncContext async -) noexcept -{ - return UnregisterForEvents(m_gameInviteSubscription, async); -} - - -HRESULT RTANotificationService::RegisterForAchievementUnlockEvents( - _In_ AsyncContext async -) noexcept -{ - return RegisterForEvents(m_achievementUnlockSubscription, async); -}; - - -HRESULT RTANotificationService::UnregisterForAchievementUnlockEvents( - _In_ AsyncContext async -) noexcept -{ - return UnregisterForEvents(m_achievementUnlockSubscription, async); + m_rtaSubscription->ResourceUri(), + AsyncContext{ m_taskQueue, + [](HRESULT hr) + { + if (FAILED(hr)) + { + LOGS_ERROR << "Failed to register with Notification Service hr=" << std::hex << hr; + } + } + }); } HRESULT RTANotificationService::RegisterWithNotificationService( @@ -124,128 +64,68 @@ HRESULT RTANotificationService::RegisterWithNotificationService( _In_ AsyncContext async ) noexcept { - std::lock_guard lock{ m_mutex }; + std::lock_guard lock{ m_mutex }; - Stringstream titleId; - titleId << AppConfig::Instance()->TitleId(); + Stringstream titleId; + titleId << AppConfig::Instance()->TitleId(); - const char platform[]{ + const char platform[]{ #if HC_PLATFORM == HC_PLATFORM_WIN32 - "Win32", + "Win32", #elif HC_PLATFORM == HC_PLATFORM_NINTENDO_SWITCH - "NintendoSwitch", + "NintendoSwitch", #endif - }; + }; - Vector notificationFilterList; - notificationFilterList.push_back({ NotificationTypeFilterSourceType::Multiplayer, 1 }); - notificationFilterList.push_back({ NotificationTypeFilterSourceType::Multiplayer, 8 }); - notificationFilterList.push_back({ NotificationTypeFilterSourceType::Achievements, 1 }); + Vector notificationFilterList; + notificationFilterList.push_back({ NotificationTypeFilterSourceType::Multiplayer, 1 }); + notificationFilterList.push_back({ NotificationTypeFilterSourceType::Multiplayer, 8 }); + notificationFilterList.push_back({ NotificationTypeFilterSourceType::Achievements, 1 }); - return NotificationService::RegisterForNotificationsHelper( - utils::create_guid(true), // applicationInstanceId - uriData, - platform, - "", - "", - notificationFilterList, - AsyncContext{ - async.Queue(), - [thisWeakPtr = std::weak_ptr{ shared_from_this() }, async](HRESULT hr) - { - if (auto pThis{ thisWeakPtr.lock() }) - { - auto derivedPtr = dynamic_cast(pThis.get()); - if (SUCCEEDED(hr)) - { - if (pThis->m_registrationStatus == RegistrationStatus::PendingUnregistration) - { - // Immediately kick off unregistration - derivedPtr->UnregisterForGameInviteEvents(); - derivedPtr->UnregisterForAchievementUnlockEvents(); - } - - return async.Complete(hr); - } - else - { - - return async.Complete(E_XBL_AUTH_RUNTIME_ERROR); - } - } - } - }); + return NotificationService::RegisterForNotificationsHelper( + utils::create_guid(true), // applicationInstanceId + uriData, + platform, + "", + "", + notificationFilterList, + std::move(async) + ); } XblFunctionContext RTANotificationService::AddGameInviteHandler( - _In_ GameInviteSubscription::MPSDInviteHandler handler + _In_ NotificationSubscription::MPSDInviteHandler handler ) noexcept { std::lock_guard lock{ m_mutex }; - - RegisterForGameInviteEvents(); - - // Subscription should always be created after registering for invites - assert(m_gameInviteSubscription); - return m_gameInviteSubscription->AddHandler(std::move(handler)); + assert(m_rtaSubscription); + return m_rtaSubscription->AddHandler(std::move(handler)); } XblFunctionContext RTANotificationService::AddGameInviteHandler( - _In_ GameInviteSubscription::MultiplayerActivityInviteHandler handler + _In_ NotificationSubscription::MultiplayerActivityInviteHandler handler ) noexcept { std::lock_guard lock{ m_mutex }; - - RegisterForGameInviteEvents(); - - // Subscription should always be created after registering for invites - assert(m_gameInviteSubscription); - return m_gameInviteSubscription->AddHandler(std::move(handler)); -} - -void RTANotificationService::RemoveGameInviteHandler( - _In_ XblFunctionContext token -) noexcept -{ - std::lock_guard lock{ m_mutex }; - if (m_gameInviteSubscription) - { - size_t remainingHandlers = m_gameInviteSubscription->RemoveHandler(token); - if (!remainingHandlers) - { - // Unregister if that was the last client handler - UnregisterForGameInviteEvents(); - } - } + assert(m_rtaSubscription); + return m_rtaSubscription->AddHandler(std::move(handler)); } XblFunctionContext RTANotificationService::AddAchievementUnlockNotificationHandler( - _In_ AchievementUnlockSubscription::EventHandler handler + _In_ NotificationSubscription::AchievementUnlockHandler handler ) noexcept { std::lock_guard lock{ m_mutex }; - - RegisterForAchievementUnlockEvents(); - - // Subscription should always be created after registering for invites - assert(m_achievementUnlockSubscription); - return m_achievementUnlockSubscription->AddHandler(std::move(handler)); + assert(m_rtaSubscription); + return m_rtaSubscription->AddHandler(std::move(handler)); } -void RTANotificationService::RemoveAchievementUnlockNotificationHandler( +void RTANotificationService::RemoveNotificationHandler( _In_ XblFunctionContext token ) noexcept { std::lock_guard lock{ m_mutex }; - if (m_achievementUnlockSubscription) - { - size_t remainingHandlers = m_achievementUnlockSubscription->RemoveHandler(token); - if (!remainingHandlers) - { - // Unregister if that was the last client handler - UnregisterForAchievementUnlockEvents(); - } - } + m_rtaSubscription->RemoveHandler(token); } NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_END diff --git a/Source/Services/Notification/RTA/game_invite_subscription.cpp b/Source/Services/Notification/RTA/notification_subscription.cpp similarity index 52% rename from Source/Services/Notification/RTA/game_invite_subscription.cpp rename to Source/Services/Notification/RTA/notification_subscription.cpp index 1d3d945f..1a4cffba 100644 --- a/Source/Services/Notification/RTA/game_invite_subscription.cpp +++ b/Source/Services/Notification/RTA/notification_subscription.cpp @@ -1,9 +1,5 @@ -// Copyright (c) Microsoft Corporation -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - - #include "pch.h" -#include "notification_internal.h" +#include "notification_subscription.h" #include "real_time_activity_manager.h" #include "multiplayer_internal.h" @@ -11,55 +7,63 @@ NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_BEGIN -GameInviteSubscription::GameInviteSubscription( - _In_ uint64_t xuid, +NotificationSubscription::NotificationSubscription( + _In_ User&& user, + _In_ TaskQueue queue, _In_ uint32_t titleId -) noexcept +) noexcept : + m_user{ std::move(user) }, + m_queue{ std::move(queue) } { Stringstream ss; - ss << "https://notify.xboxlive.com/users/xuid(" << xuid << ")/deviceId/current/titleId/" << titleId; + ss << "https://notify.xboxlive.com/users/xuid(" << m_user.Xuid() << ")/deviceId/current/titleId/" << titleId; m_resourceUri = ss.str(); } -const String& GameInviteSubscription::ResourceUri() const noexcept +const String& NotificationSubscription::ResourceUri() const noexcept { return m_resourceUri; } -XblFunctionContext GameInviteSubscription::AddHandler( - MPSDInviteHandler handler -) noexcept +XblFunctionContext NotificationSubscription::AddHandler(MPSDInviteHandler&& handler) noexcept { std::lock_guard lock{ m_mutex }; m_mpsdInviteHandlers[m_nextToken] = std::move(handler); return m_nextToken++; } -XblFunctionContext GameInviteSubscription::AddHandler( - MultiplayerActivityInviteHandler handler -) noexcept +XblFunctionContext NotificationSubscription::AddHandler(MultiplayerActivityInviteHandler&& handler) noexcept { std::lock_guard lock{ m_mutex }; m_mpaInviteHandlers[m_nextToken] = std::move(handler); return m_nextToken++; } -size_t GameInviteSubscription::RemoveHandler( - XblFunctionContext token -) noexcept +XblFunctionContext NotificationSubscription::AddHandler(AchievementUnlockHandler&& handler) noexcept { std::lock_guard lock{ m_mutex }; - m_mpaInviteHandlers.erase(token); - m_mpsdInviteHandlers.erase(token); - return m_mpaInviteHandlers.size() + m_mpsdInviteHandlers.size(); + m_achievementUnlockHandlers[m_nextToken] = std::move(handler); + return m_nextToken++; } -void GameInviteSubscription::OnEvent( +size_t NotificationSubscription::RemoveHandler(XblFunctionContext token) noexcept +{ + std::lock_guard lock{ m_mutex }; + + m_mpaInviteHandlers.erase(token); + m_mpsdInviteHandlers.erase(token); + m_achievementUnlockHandlers.erase(token); + + // return the total number of handlers remaining + return m_mpaInviteHandlers.size() + m_mpsdInviteHandlers.size() + m_achievementUnlockHandlers.size(); +} + +void NotificationSubscription::OnEvent( _In_ const JsonValue& data ) noexcept { - // MPSD Invites and MPA invites are both delivered via the same RTA subscription, but the payloads for each - // are slightly different. We examine the json here to distinguish between the two. + // Notifications from several services are delivered via the same RTA subscription. Examine the payload to + // distinguish between them. if (!data.IsObject()) { @@ -102,12 +106,73 @@ void GameInviteSubscription::OnEvent( } } } + else if (data.HasMember("KickNotification")) + { + XalUserGetTokenAndSignatureArgs args; + args.forceRefresh = true; + args.url = "https://xboxlive.com/"; + args.method = "GET"; + + auto async = MakeUnique(); + async->queue = m_queue.GetHandle(); + async->callback = [](XAsyncBlock* async) + { + // Take ownership of async block + UniquePtr asyncUnique{ async }; + + // Get the result even though we aren't using it so Xal can release it + size_t bufferSize{}; + HRESULT hr = XalUserGetTokenAndSignatureSilentlyResultSize(async, &bufferSize); + + if (SUCCEEDED(hr)) + { + Vector buffer(bufferSize); + XalUserGetTokenAndSignatureData* xalTokenSignatureData{ nullptr }; + + hr = XalUserGetTokenAndSignatureSilentlyResult(async, bufferSize, buffer.data(), &xalTokenSignatureData, nullptr); + UNREFERENCED_PARAMETER(hr); + } + }; + + if (SUCCEEDED(XalUserGetTokenAndSignatureSilentlyAsync(m_user.Handle(), &args, async.get()))) + { + async.release(); + } + } + else if (data.HasMember("service")) + { + String originatingService; + JsonUtils::ExtractJsonString(data, "service", originatingService); + + if (originatingService == "achievements") + { + // Deserialize and send invite notification + auto deserializationResult = AchievementUnlockEvent::Deserialize(data); + if (Succeeded(deserializationResult)) + { + std::unique_lock lock{ m_mutex }; + auto handlers{ m_achievementUnlockHandlers }; + lock.unlock(); + + for (auto& handler : handlers) + { + handler.second(deserializationResult.Payload()); + } + } + } + } else { LOGS_ERROR << __FUNCTION__ << ": Ignoring unrecognized payload"; } } +void NotificationSubscription::OnResync() noexcept +{ + // Don't think there is much we can do here - just log an error + LOGS_ERROR << __FUNCTION__ << ": Notification service events may have been missed"; +} + GameInviteNotificationEventArgs::GameInviteNotificationEventArgs( const GameInviteNotificationEventArgs& other ) noexcept @@ -230,11 +295,100 @@ Result MultiplayerActivityInviteData::Deserialize return data; } -void GameInviteSubscription::OnResync() noexcept +AchievementUnlockEvent::AchievementUnlockEvent(AchievementUnlockEvent&& event) noexcept : + m_achievementId(std::move(event.m_achievementId)), + m_achievementName(std::move(event.m_achievementName)), + m_achievementDescription(std::move(event.m_achievementDescription)), + m_achievementIconUri(std::move(event.m_achievementIconUri)), + m_deepLink(event.m_deepLink) { - // Don't think there is much we can do here - just log an error - LOGS_ERROR << __FUNCTION__ << ": Game invites may have been discarded by RTA service"; + // strings for the C API + achievementId = m_achievementId.c_str(); + achievementName = m_achievementName.c_str(); + achievementDescription = m_achievementDescription.c_str(); + achievementIcon = m_achievementIconUri.c_str(); + titleId = event.titleId; + gamerscore = event.gamerscore; + rarityPercentage = event.rarityPercentage; + rarityCategory = event.rarityCategory; + timeUnlocked = event.timeUnlocked; + xboxUserId = event.xboxUserId; +} + +AchievementUnlockEvent::AchievementUnlockEvent(const AchievementUnlockEvent& event) : + m_achievementId(event.m_achievementId), + m_achievementName(event.m_achievementName), + m_achievementDescription(event.m_achievementDescription), + m_achievementIconUri(event.m_achievementIconUri), + m_deepLink(event.m_deepLink) +{ + // strings for the C API + achievementId = m_achievementId.c_str(); + achievementName = m_achievementName.c_str(); + achievementDescription = m_achievementDescription.c_str(); + achievementIcon = m_achievementIconUri.c_str(); + + titleId = event.titleId; + gamerscore = event.gamerscore; + rarityPercentage = event.rarityPercentage; + rarityCategory = event.rarityCategory; + timeUnlocked = event.timeUnlocked; + xboxUserId = event.xboxUserId; +} + + +Result AchievementUnlockEvent::Deserialize(_In_ const JsonValue& json) noexcept +{ + AchievementUnlockEvent result{}; + if (!json.IsObject()) + { + return result; + } + + double rarityPercentage = 0.0; // We are guaranteed that rarityPercentage is only float, but can only extract as double. + uint64_t titleId = 0; // We are guaranteed that titleId is only uint32, but can only extract as uint64. + String rarityCategory; // Will be converted into an enum value after extraction + + RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonString(json, "achievementId", result.m_achievementId, true)); + + RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonString(json, "achievementDescription", result.m_achievementDescription, false)); + + RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonString(json, "achievementName", result.m_achievementName, true)); + + RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonString(json, "achievementIcon", result.m_achievementIconUri, true)); + + RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonStringToUInt64(json, "titleId", titleId, true)); + + RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonUInt64(json, "gamerscore", result.gamerscore, true)); + + RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonXuid(json, "xuid", result.xboxUserId, true)); + + RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonString(json, "extendedInfoUrl", result.m_deepLink, true)); + + // RarityPercentage and rarityCategory are only in payload version 2 so make them optional + RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonDouble(json, "rarityPercentage", rarityPercentage, false)); + + RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonString(json, "rarityCategory", rarityCategory, false)); + + RETURN_HR_IF_FAILED(JsonUtils::ExtractJsonTimeT(json, "unlockTime", result.timeUnlocked)); + + result.titleId = static_cast(titleId); + result.rarityPercentage = static_cast(rarityPercentage); + result.rarityCategory = EnumValue(rarityCategory.c_str()); + + // strings for the C API + result.achievementId = result.m_achievementId.c_str(); + result.achievementName = result.m_achievementName.c_str(); + if (!result.m_achievementDescription.empty()) + { + result.achievementDescription = result.m_achievementDescription.c_str(); + } + result.achievementIcon = result.m_achievementIconUri.c_str(); + result.m_deepLink = result.m_deepLink.c_str(); + + return result; } NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_END + #endif \ No newline at end of file diff --git a/Source/Services/Notification/RTA/notification_subscription.h b/Source/Services/Notification/RTA/notification_subscription.h new file mode 100644 index 00000000..cd260b67 --- /dev/null +++ b/Source/Services/Notification/RTA/notification_subscription.h @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include "xsapi-c/game_invite_c.h" +#include "xsapi-c/achievements_c.h" +#include "xsapi-c/multiplayer_activity_c.h" +#include "real_time_activity_subscription.h" + +#if HC_PLATFORM == HC_PLATFORM_WIN32 || HC_PLATFORM_IS_EXTERNAL + +NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_BEGIN + +// Event args for MPSD game invites +struct GameInviteNotificationEventArgs : public XblGameInviteNotificationEventArgs +{ +public: + GameInviteNotificationEventArgs() noexcept = default; + GameInviteNotificationEventArgs(const GameInviteNotificationEventArgs& other) noexcept; + GameInviteNotificationEventArgs& operator=(GameInviteNotificationEventArgs other) noexcept = delete; + + static Result Deserialize( + _In_ const JsonValue& json + ) noexcept; + +private: + String m_inviteHandleId; + String m_inviteProtocol; + String m_senderImageUrl; +}; + +struct MultiplayerActivityInviteData : public XblMultiplayerActivityInviteData +{ +public: + MultiplayerActivityInviteData() noexcept = default; + MultiplayerActivityInviteData(const MultiplayerActivityInviteData& other) noexcept; + MultiplayerActivityInviteData& operator=(MultiplayerActivityInviteData other) noexcept = delete; + + static Result Deserialize( + const JsonValue& json + ) noexcept; + +private: + String m_senderImageUrl; + String m_titleName; + String m_titleImageUrl; + String m_connectionString; +}; + +struct AchievementUnlockEvent : public XblAchievementUnlockEvent +{ +public: + + AchievementUnlockEvent() = default; + AchievementUnlockEvent(AchievementUnlockEvent&& event) noexcept; + AchievementUnlockEvent(const AchievementUnlockEvent& event); + + static Result Deserialize(_In_ const JsonValue& json) noexcept; + +private: + + String m_achievementId; + String m_achievementDescription; + String m_achievementName; + String m_achievementIconUri; + String m_deepLink; +}; + +class NotificationSubscription : public real_time_activity::Subscription +{ +public: + NotificationSubscription( + User&& user, + TaskQueue queue, + uint32_t titleId + ) noexcept; + + const String& ResourceUri() const noexcept; + + using MPSDInviteHandler = Function; + using MultiplayerActivityInviteHandler = Function; + using AchievementUnlockHandler = Function; + + XblFunctionContext AddHandler(MPSDInviteHandler&& handler) noexcept; + XblFunctionContext AddHandler(MultiplayerActivityInviteHandler&& handler) noexcept; + XblFunctionContext AddHandler(AchievementUnlockHandler&& handler) noexcept; + + size_t RemoveHandler(XblFunctionContext token) noexcept; + +protected: + void OnEvent(_In_ const JsonValue& event) noexcept override; + void OnResync() noexcept override; + +private: + User m_user; + TaskQueue m_queue; + std::mutex m_mutex; + XblFunctionContext m_nextToken{ 0 }; + + Map m_mpsdInviteHandlers; + Map m_mpaInviteHandlers; + Map m_achievementUnlockHandlers; +}; + +NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_END +#endif \ No newline at end of file diff --git a/Source/Services/Notification/RTA/spop_kick_subscription.cpp b/Source/Services/Notification/RTA/spop_kick_subscription.cpp deleted file mode 100644 index 516ad4e3..00000000 --- a/Source/Services/Notification/RTA/spop_kick_subscription.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "pch.h" -#include "spop_kick_subscription.h" -#include "real_time_activity_manager.h" -#include "multiplayer_internal.h" - -#if HC_PLATFORM == HC_PLATFORM_WIN32 || HC_PLATFORM_IS_EXTERNAL - -NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_BEGIN - -SpopKickSubscription::SpopKickSubscription( - _In_ User&& user, - _In_ uint32_t titleId -) noexcept : - m_user(std::move(user)) -{ - Stringstream ss; - ss << "https://notify.xboxlive.com/users/xuid(" << m_user.Xuid() << ")/deviceId/current/titleId/" << titleId; - m_resourceUri = ss.str(); -} - -const String& SpopKickSubscription::ResourceUri() const noexcept -{ - return m_resourceUri; -} - -void SpopKickSubscription::OnEvent( - _In_ const JsonValue& data -) noexcept -{ - auto datastr = JsonUtils::SerializeJson(data); - UNREFERENCED_PARAMETER(datastr); - if (data.IsObject() && data.HasMember("KickNotification")) - { - auto args = std::make_unique(); - args->forceRefresh = true; - args->url = "https://xboxlive.com/"; - args->method = "GET"; - - auto async = std::make_unique(); - async->callback = [](XAsyncBlock* async) - { - size_t bufferSize{}; - auto h = XalUserGetTokenAndSignatureSilentlyResultSize(async, &bufferSize); - UNREFERENCED_PARAMETER(h); - }; - - if (SUCCEEDED(XalUserGetTokenAndSignatureSilentlyAsync(m_user.Handle(), args.get(), async.get()))) - { - args.release(); - async.release(); - } - } -} - -void SpopKickSubscription::OnResync() noexcept -{ - // Don't think there is much we can do here - just log an error - LOGS_ERROR << __FUNCTION__ << ": SPOP notifications event may have been discarded by RTA service"; -} -NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_END -#endif \ No newline at end of file diff --git a/Source/Services/Notification/RTA/spop_kick_subscription.h b/Source/Services/Notification/RTA/spop_kick_subscription.h deleted file mode 100644 index 3750ae39..00000000 --- a/Source/Services/Notification/RTA/spop_kick_subscription.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#pragma once -#include "xsapi-c/game_invite_c.h" -#include "real_time_activity_subscription.h" - -#if HC_PLATFORM == HC_PLATFORM_WIN32 || HC_PLATFORM_IS_EXTERNAL - -NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_BEGIN - -class SpopKickSubscription : public real_time_activity::Subscription -{ -public: - SpopKickSubscription( - User&& user, - uint32_t titleId - ) noexcept; - - const String& ResourceUri() const noexcept; - -protected: - void OnEvent(_In_ const JsonValue& event) noexcept override; - void OnResync() noexcept override; - -private: - User m_user; -}; -NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_END -#endif \ No newline at end of file diff --git a/Source/Services/Notification/notification_api.cpp b/Source/Services/Notification/notification_api.cpp index 677139e9..a7d37a37 100644 --- a/Source/Services/Notification/notification_api.cpp +++ b/Source/Services/Notification/notification_api.cpp @@ -3,7 +3,6 @@ #include "pch.h" -#include "RTA/achievement_unlock_subscription.h" #include "notification_internal.h" #include "multiplayer_internal.h" #include "xbox_live_context_internal.h" @@ -200,22 +199,10 @@ STDAPI XblGameInviteRegisterForEventAsync( { switch (op) { - case XAsyncOp::DoWork: + case XAsyncOp::Begin: { - auto rtaNotificationService = std::dynamic_pointer_cast(xblContext->NotificationService()); - RETURN_HR_IF_FAILED(rtaNotificationService->RegisterForGameInviteEvents({ data->async->queue, - [ - asyncBlock{ data->async } - ] - (Result result) - { - // We want to return a dummy subscription handle to maintain legacy behavior, so make sure to - // indicate that there is a result payload - XAsyncComplete(asyncBlock, result.Hresult(), sizeof(XblRealTimeActivitySubscriptionHandle)); - } - })); -// Do we need E-PENDING - return E_PENDING; + XAsyncComplete(data->async, S_OK, sizeof(XblRealTimeActivitySubscriptionHandle)); + return S_OK; } case XAsyncOp::GetResult: { @@ -269,13 +256,11 @@ STDAPI XblGameInviteUnregisterForEventAsync( { switch (op) { - case XAsyncOp::DoWork: + case XAsyncOp::Begin: { - - auto rtaNotificationService = std::dynamic_pointer_cast(xblContext->NotificationService()); - RETURN_HR_IF_FAILED(rtaNotificationService->UnregisterForGameInviteEvents(data->async)); Delete(subscriptionHandle); - return E_PENDING; + XAsyncComplete(data->async, S_OK, 0); + return S_OK; } default: return S_OK; } @@ -295,7 +280,7 @@ STDAPI_(XblFunctionContext) XblGameInviteAddNotificationHandler( } auto rtaNotificationService = std::dynamic_pointer_cast(xblContextHandle->NotificationService()); - return rtaNotificationService->AddGameInviteHandler(GameInviteSubscription::MPSDInviteHandler{ + return rtaNotificationService->AddGameInviteHandler(NotificationSubscription::MPSDInviteHandler{ [ handler, context ] @@ -321,7 +306,7 @@ STDAPI_(void) XblGameInviteRemoveNotificationHandler( if (xblContextHandle) { auto rtaNotificationService = std::dynamic_pointer_cast(xblContextHandle->NotificationService()); - rtaNotificationService->RemoveGameInviteHandler(token); + rtaNotificationService->RemoveNotificationHandler(token); } } @@ -361,7 +346,7 @@ STDAPI_(void) XblAchievementUnlockRemoveNotificationHandler( ) XBL_NOEXCEPT { auto rtaNotificationService = std::dynamic_pointer_cast(xblContextHandle->NotificationService()); - rtaNotificationService->RemoveAchievementUnlockNotificationHandler(functionContext); + rtaNotificationService->RemoveNotificationHandler(functionContext); } diff --git a/Source/Services/Notification/notification_internal.h b/Source/Services/Notification/notification_internal.h index 0733598b..b9be9278 100644 --- a/Source/Services/Notification/notification_internal.h +++ b/Source/Services/Notification/notification_internal.h @@ -4,11 +4,8 @@ #pragma once #if HC_PLATFORM == HC_PLATFORM_WIN32 || HC_PLATFORM_IS_EXTERNAL -#include "xsapi-c/game_invite_c.h" -#include "xsapi-c/multiplayer_activity_c.h" #include "real_time_activity_subscription.h" -#include "RTA/achievement_unlock_subscription.h" -#include "RTA/spop_kick_subscription.h" +#include "RTA/notification_subscription.h" #endif NAMESPACE_MICROSOFT_XBOX_SERVICES_NOTIFICATION_CPP_BEGIN @@ -99,117 +96,35 @@ public: #endif #if HC_PLATFORM == HC_PLATFORM_WIN32 || HC_PLATFORM_IS_EXTERNAL -// Event args for MPSD game invites -struct GameInviteNotificationEventArgs : public XblGameInviteNotificationEventArgs -{ -public: - GameInviteNotificationEventArgs() noexcept = default; - GameInviteNotificationEventArgs(const GameInviteNotificationEventArgs& other) noexcept; - GameInviteNotificationEventArgs& operator=(GameInviteNotificationEventArgs other) noexcept = delete; - - static Result Deserialize( - _In_ const JsonValue& json - ) noexcept; - -private: - String m_inviteHandleId; - String m_inviteProtocol; - String m_senderImageUrl; -}; - -struct MultiplayerActivityInviteData : public XblMultiplayerActivityInviteData -{ -public: - MultiplayerActivityInviteData() noexcept = default; - MultiplayerActivityInviteData(const MultiplayerActivityInviteData& other) noexcept; - MultiplayerActivityInviteData& operator=(MultiplayerActivityInviteData other) noexcept = delete; - - static Result Deserialize( - const JsonValue& json - ) noexcept; - -private: - String m_senderImageUrl; - String m_titleName; - String m_titleImageUrl; - String m_connectionString; -}; - -class GameInviteSubscription : public real_time_activity::Subscription -{ -public: - GameInviteSubscription( - _In_ uint64_t xuid, - _In_ uint32_t titleId - ) noexcept; - - const String& ResourceUri() const noexcept; - - typedef Function MPSDInviteHandler; - typedef Function MultiplayerActivityInviteHandler; - - XblFunctionContext AddHandler(MPSDInviteHandler handler) noexcept; - XblFunctionContext AddHandler(MultiplayerActivityInviteHandler handler) noexcept; - size_t RemoveHandler(XblFunctionContext token) noexcept; - -protected: - void OnEvent(_In_ const JsonValue& data) noexcept override; - void OnResync() noexcept override; - -private: - UnorderedMap m_mpsdInviteHandlers; - UnorderedMap m_mpaInviteHandlers; - XblFunctionContext m_nextToken{ 0 }; - - std::mutex m_mutex; -}; class RTANotificationService : public NotificationService { public: RTANotificationService( _In_ User&& user, + _In_ const TaskQueue& taskQueue, _In_ std::shared_ptr contextSettings, _In_ std::shared_ptr rtaManager ) noexcept; - HRESULT RegisterForSpopNotificationEvents() noexcept; + ~RTANotificationService() noexcept; - // Keeping these APIs public to continue supporting XblGameInviteRegisterForEventResult and XblGameInviteUnregisterForEventAsync - HRESULT RegisterForGameInviteEvents( - _In_ AsyncContext async = {} - ) noexcept; + HRESULT Initialize() noexcept; - HRESULT UnregisterForGameInviteEvents( - _In_ AsyncContext async = {} - ) noexcept; - - HRESULT RegisterForAchievementUnlockEvents( - _In_ AsyncContext async = {} - ) noexcept; - - HRESULT UnregisterForAchievementUnlockEvents( - _In_ AsyncContext async = {} + XblFunctionContext AddGameInviteHandler( + _In_ NotificationSubscription::MPSDInviteHandler handler ) noexcept; XblFunctionContext AddGameInviteHandler( - _In_ GameInviteSubscription::MPSDInviteHandler handler + _In_ NotificationSubscription::MultiplayerActivityInviteHandler handler ) noexcept; - XblFunctionContext AddGameInviteHandler( - _In_ GameInviteSubscription::MultiplayerActivityInviteHandler handler - ) noexcept; - - void RemoveGameInviteHandler( + void RemoveNotificationHandler( _In_ XblFunctionContext token ) noexcept; XblFunctionContext AddAchievementUnlockNotificationHandler( - _In_ AchievementUnlockSubscription::EventHandler handler - ) noexcept; - - void RemoveAchievementUnlockNotificationHandler( - _In_ XblFunctionContext token + _In_ NotificationSubscription::AchievementUnlockHandler handler ) noexcept; HRESULT RegisterWithNotificationService( @@ -218,19 +133,9 @@ public: ) noexcept override; private: - template - HRESULT RegisterForEvents(std::shared_ptr& subscription, - AsyncContext async) noexcept; - - template - HRESULT UnregisterForEvents(std::shared_ptr& subscription, - AsyncContext async) noexcept; - + TaskQueue m_taskQueue; std::shared_ptr m_rtaManager; - - std::shared_ptr m_gameInviteSubscription; - std::shared_ptr m_achievementUnlockSubscription; - std::shared_ptr m_spopNotificationSubscription; + std::shared_ptr m_rtaSubscription; }; #elif HC_PLATFORM == HC_PLATFORM_UWP class UWPNotificationService : public NotificationService diff --git a/Source/Services/Social/Manager/social_graph.cpp b/Source/Services/Social/Manager/social_graph.cpp index b95fadf7..6be7d195 100644 --- a/Source/Services/Social/Manager/social_graph.cpp +++ b/Source/Services/Social/Manager/social_graph.cpp @@ -50,6 +50,9 @@ struct ServiceCallManager : public std::enable_shared_from_this lock) noexcept; HRESULT PollPeopleHubServiceCall(std::unique_lock lock) noexcept; @@ -367,6 +370,15 @@ void SocialGraph::RegisterGroup(std::shared_ptr group if (iter == m_groups.end() || iter->second == GroupInitializationStage::Complete) { m_groups[group] = GroupInitializationStage::Pending; + + // Check if the filter is one that relies on title history but TitleHistoryLevel is not set + if ((group->presenceFilter == XblPresenceFilter::TitleOffline || + group->presenceFilter == XblPresenceFilter::TitleOnlineOutsideTitle || + group->presenceFilter == XblPresenceFilter::AllTitle) && + (m_serviceCallManager->GetDetailLevel() & XblSocialManagerExtraDetailLevel::TitleHistoryLevel) != XblSocialManagerExtraDetailLevel::TitleHistoryLevel) + { + LOGS_DEBUG << "TitleOffline, TitleOnlineOutsideTitle, and AllTitle filters require XblSocialManagerExtraDetailLevel::TitleHistoryLevel to be set for this user"; + } } } @@ -982,4 +994,9 @@ HRESULT ServiceCallManager::PollPeopleHubServiceCall(std::unique_lock&& trackedUsers ) noexcept : type{ XblSocialUserGroupType::UserListType }, - m_trackedUsersView{ std::move(trackedUsers) }, - m_trackedUsers{ m_trackedUsersView.begin(), m_trackedUsersView.end() }, m_localUser{ socialGraph->LocalUser() }, - m_graph{ socialGraph } + m_graph{ socialGraph }, + m_trackedUsersView{ std::move(trackedUsers) }, + m_trackedUsers{ m_trackedUsersView.begin(), m_trackedUsersView.end() } { m_usersView.reserve(m_trackedUsersView.size()); socialGraph->TrackUsers(m_trackedUsersView); diff --git a/Source/Shared/build_version.h b/Source/Shared/build_version.h index 8b6483be..2d6f8507 100644 --- a/Source/Shared/build_version.h +++ b/Source/Shared/build_version.h @@ -9,4 +9,4 @@ //********************************************************* #pragma once -#define XBOX_SERVICES_API_VERSION_STRING "2021.02.20210318.2" +#define XBOX_SERVICES_API_VERSION_STRING "2021.04.20210317.0" diff --git a/Source/Shared/user.cpp b/Source/Shared/user.cpp index 2654e351..61d6525d 100644 --- a/Source/Shared/user.cpp +++ b/Source/Shared/user.cpp @@ -12,7 +12,7 @@ User::User(XblUserHandle userHandle) noexcept User::User(User&& other) noexcept - : m_handle{ other.m_handle }, m_localId { std::move(other.m_localId) }, m_xuid {other.m_xuid } + : m_handle{ other.m_handle }, m_xuid{ other.m_xuid }, m_localId { std::move(other.m_localId) } { Map::iterator it = other.m_gamertags.begin(); @@ -349,7 +349,7 @@ void User::SetTokenExpired(uint64_t xuid) noexcept } -Result User::RegisterChangeEventHandler( +Result User::RegisterChangeEventHandler( UserChangeEventHandler handler ) noexcept { @@ -383,14 +383,14 @@ Result User::RegisterChangeEventHandler( state->SetUserChangeHandler(token.token, context); } } - return Result{ static_cast(token.token), hr }; + return Result{token.token, hr }; } void User::UnregisterChangeEventHandle( - XblFunctionContext token + uint64_t token ) noexcept { - XalUserUnregisterChangeEventHandler(XalRegistrationToken{ static_cast(token) }); + XalUserUnregisterChangeEventHandler(XalRegistrationToken{ token }); auto state{ GlobalState::Get() }; if (state) { diff --git a/Source/Shared/user.h b/Source/Shared/user.h index 9486b390..9e4b0785 100644 --- a/Source/Shared/user.h +++ b/Source/Shared/user.h @@ -63,12 +63,12 @@ public: static void SetTokenExpired(uint64_t xuid) noexcept; - static Result RegisterChangeEventHandler( + static Result RegisterChangeEventHandler( UserChangeEventHandler handler ) noexcept; static void UnregisterChangeEventHandle( - XblFunctionContext token + uint64_t token ) noexcept; private: @@ -90,7 +90,7 @@ class Result { public: Result(User&& user) : m_payload{ std::move(user) } {} - Result(User&& user, HRESULT error) : m_payload{ std::move(user) }, m_result{ error } {} + Result(User&& user, HRESULT error) : m_result{ error }, m_payload{ std::move(user) } {} Result(Result&& other) = default; Result(const Result& other) = delete; diff --git a/Tests/ApiExplorer/APIExplorer.Shared.vcxitems b/Tests/ApiExplorer/APIExplorer.Shared.vcxitems index 58ac1317..7ee2be20 100644 --- a/Tests/ApiExplorer/APIExplorer.Shared.vcxitems +++ b/Tests/ApiExplorer/APIExplorer.Shared.vcxitems @@ -514,7 +514,7 @@ true - + true diff --git a/Tests/ApiExplorer/APIs/apis_xblc_achievement_unlock_notification.cpp b/Tests/ApiExplorer/APIs/apis_xblc_achievement_unlock_notification.cpp index dba9d10b..d8c4a21f 100644 --- a/Tests/ApiExplorer/APIs/apis_xblc_achievement_unlock_notification.cpp +++ b/Tests/ApiExplorer/APIs/apis_xblc_achievement_unlock_notification.cpp @@ -130,24 +130,33 @@ void UnsetHandler(XblFunctionContext id) namespace lua { + int XblAchievementUnlockAddNotificationHandler(lua_State *L) + { + auto id = SetHandler(); + lua_pushinteger(L, id); + LuaReturnHR(L, S_OK, 1); + return 0; + } + + int XblAchievementUnlockRemoveNotificationHandler(lua_State *L) + { + uint32_t id = GetUint32FromLua(L, 1, 0); + UnsetHandler(id); + return 0; + } int RunAchievementUnlock(lua_State *L) { std::string achievementId = luaL_checkstring(L, 1); - + HRESULT hr = 0; - XblFunctionContext id; errorCode = status::ERROR_NO_MSG; - id = SetHandler(); - Sleep(1000); - - hr = UnlockAchievement(achievementId); - - lua_pushinteger(L, id); - return LuaReturnHR(L, hr, 1); + hr = UnlockAchievement(achievementId); + + return LuaReturnHR(L, hr); } @@ -191,6 +200,8 @@ void SetupAPIs_XblAchievementUnlockNotification() lua_register(Data()->L, "Cleanup", xbl::apirunner::lua::Cleanup); lua_register(Data()->L, "CheckStatus", xbl::apirunner::lua::CheckStatus); lua_register(Data()->L, "IsAchievementLocked", xbl::apirunner::lua::IsAchievementLocked); + lua_register(Data()->L, "XblAchievementUnlockAddNotificationHandler", xbl::apirunner::lua::XblAchievementUnlockAddNotificationHandler); + lua_register(Data()->L, "XblAchievementUnlockRemoveNotificationHandler", xbl::apirunner::lua::XblAchievementUnlockRemoveNotificationHandler); } #endif \ No newline at end of file diff --git a/Tests/ApiExplorer/APIs/apis_xblc_stats2017.cpp b/Tests/ApiExplorer/APIs/apis_xblc_stats2017.cpp index 34d7ae1c..483e66a1 100644 --- a/Tests/ApiExplorer/APIs/apis_xblc_stats2017.cpp +++ b/Tests/ApiExplorer/APIs/apis_xblc_stats2017.cpp @@ -167,7 +167,8 @@ int XblTitleManagedStatsWriteAsyncWithSVD_Lua(lua_State *L) { LogToFile("XblTitleManagedStatsWriteAsyncWithSVD: hr = %s", ConvertHR(hr).data()); s_svd = nullptr; - return LuaReturnHR(L, hr); + CallLuaFunction("OnXblTitleManagedStatsUnableToGetTokenAndSignature"); + return LuaReturnHR(L, S_OK); } } @@ -186,6 +187,13 @@ int XblTitleManagedStatsWriteAsyncWithSVD_Lua(lua_State *L) { std::unique_ptr asyncBlockPtr{ asyncBlock }; // Take over ownership of the XAsyncBlock* HRESULT hr = XAsyncGetStatus(asyncBlock, false); + + if (hr == E_FAIL) + { + CallLuaFunction("OnXblTitleManagedStatsUnableToGetTokenAndSignature"); + return; + } + CallLuaFunctionWithHr(hr, "OnXblTitleManagedStatsWriteAsyncWithSVD"); // CODE SNIP SKIP }; @@ -238,6 +246,13 @@ int XblTitleManagedStatsWriteAsync_Lua(lua_State *L) { LogToScreen("XblTitleManagedStatsWriteAsync: 0x%0.8x", hr); } + + if (hr == E_FAIL) + { + CallLuaFunction("OnXblTitleManagedStatsUnableToGetTokenAndSignature"); + return; + } + CallLuaFunctionWithHr(hr, "OnXblTitleManagedStatsWriteAsync"); // CODE SNIP SKIP }; @@ -270,7 +285,8 @@ int XblTitleManagedStatsUpdateStatsAsync_Lua(lua_State* L) { LogToFile("XblTitleManagedStatsWriteAsyncWithSVD: hr = %s", ConvertHR(hr).data()); s_svd = nullptr; - return LuaReturnHR(L, hr); + CallLuaFunction("OnXblTitleManagedStatsUnableToGetTokenAndSignature"); + return LuaReturnHR(L, S_OK); } } @@ -297,6 +313,13 @@ int XblTitleManagedStatsUpdateStatsAsync_Lua(lua_State* L) { std::unique_ptr asyncBlockPtr{ asyncBlock }; // Take over ownership of the XAsyncBlock* HRESULT hr = XAsyncGetStatus(asyncBlock, false); + + if (hr == E_FAIL) + { + CallLuaFunction("OnXblTitleManagedStatsUnableToGetTokenAndSignature"); + return; + } + CallLuaFunctionWithHr(hr, "OnXblTitleManagedStatsUpdateStatsAsync"); // CODE SNIP SKIP }; @@ -329,7 +352,8 @@ int XblTitleManagedStatsDeleteStatsAsync_Lua(lua_State* L) { LogToFile("XblTitleManagedStatsWriteAsyncWithSVD: hr = %s", ConvertHR(hr).data()); s_svd = nullptr; - return LuaReturnHR(L, hr); + CallLuaFunction("OnXblTitleManagedStatsUnableToGetTokenAndSignature"); + return LuaReturnHR(L, S_OK); } } @@ -343,6 +367,13 @@ int XblTitleManagedStatsDeleteStatsAsync_Lua(lua_State* L) { std::unique_ptr asyncBlockPtr{ asyncBlock }; // Take over ownership of the XAsyncBlock* HRESULT hr = XAsyncGetStatus(asyncBlock, false); + + if (hr == E_FAIL) + { + CallLuaFunction("OnXblTitleManagedStatsUnableToGetTokenAndSignature"); + return; + } + CallLuaFunctionWithHr(hr, "OnXblTitleManagedStatsDeleteStatsAsync"); // CODE SNIP SKIP }; @@ -401,7 +432,8 @@ int ClearSVD_Lua(lua_State* L) { LogToFile("XblTitleManagedStatsWriteAsyncWithSVD: hr = %s", ConvertHR(hr).data()); s_svd = nullptr; - return LuaReturnHR(L, hr); + lua_pushnumber(L, 1); + return 1; } } s_svd->ClearStats(); diff --git a/Tests/ApiExplorer/APIs/apis_xblc_title_storage.cpp b/Tests/ApiExplorer/APIs/apis_xblc_title_storage.cpp index 7e977a2a..417281dd 100644 --- a/Tests/ApiExplorer/APIs/apis_xblc_title_storage.cpp +++ b/Tests/ApiExplorer/APIs/apis_xblc_title_storage.cpp @@ -589,26 +589,30 @@ int XblTitleStorageUploadBinaryBlobAsync_Lua(lua_State *L) return LuaReturnHR(L, hr); } -int RestCallForEachBlob_Lua(lua_State *L) +int RestCallToDownloadJsonBlob_Lua(lua_State *L) { CreateQueueIfNeeded(); - auto response = Data()->responseString; + auto response = Data()->metadataResponseString; HRESULT hr = S_OK; - auto result = DeserializeResult("https://titlestorage.xboxlive.com/universalplatform/users/xuid(2814654044759996)/scids/00000000-0000-0000-0000-000076029b4d/data/", response); + std::string methodName = GetStringFromLua(L, 1, "GET"); + char url[300]; + sprintf_s(url, "https://titlestorage.xboxlive.com/json/users/xuid(%" PRIu64 ")/scids/00000000-0000-0000-0000-000076029b4d/data/", Data()->xboxUserId); + + auto result = DeserializeResult(url, response); for (const auto& blobMetadata : result.m_items) { // Download the blob - XblHttpCallHandle output; - hr = XblHttpCallCreate(Data()->xboxLiveContext, "GET", blobMetadata.blobPath.c_str(), &output); - XblHttpCallRequestSetHeader(output, "Content-Type", "application/json; charset=utf-8", true); - XblHttpCallRequestSetHeader(output, "Accept-Language", "en-US,en", true); - XblHttpCallRequestSetHeader(output, "x-xbl-contract-version", "2", true); - Data()->downloadHttpCalls.push_back(output); + XblHttpCallHandle httpCallHandle; + hr = XblHttpCallCreate(Data()->xboxLiveContext, "GET", blobMetadata.blobPath.c_str(), &httpCallHandle); + XblHttpCallRequestSetHeader(httpCallHandle, "Content-Type", "application/json; charset=utf-8", true); + XblHttpCallRequestSetHeader(httpCallHandle, "Accept-Language", "en-US,en", true); + XblHttpCallRequestSetHeader(httpCallHandle, "x-xbl-contract-version", "2", true); + Data()->titleStorageHttpCalls.push_back(httpCallHandle); auto asyncBlock = std::make_unique(); asyncBlock->queue = Data()->queue; - asyncBlock->context = Data()->downloadHttpCalls[blobMetadata.positionInList]; + asyncBlock->context = Data()->titleStorageHttpCalls[blobMetadata.positionInList]; asyncBlock->callback = [](XAsyncBlock* asyncBlock) { std::unique_ptr asyncBlockPtr{ asyncBlock }; // Take over ownership of the XAsyncBlock* @@ -621,9 +625,12 @@ int RestCallForEachBlob_Lua(lua_State *L) const char* responseString; hr = XblHttpCallGetResponseString(httpCall, &responseString); - LogToScreen("Response String: length %d, hr=%s", strlen(responseString), ConvertHR(hr).c_str()); + LogToFile("Response String: length %d, hr=%s", strlen(responseString), ConvertHR(hr).c_str()); + LogToFile("Response: %s", responseString); CHECKHR(hr); + Data()->blobResponseStrings.push_back(responseString); + uint32_t statusCode; hr = XblHttpCallGetStatusCode(httpCall, &statusCode); LogToScreen("Status Code: %d, hr=%s", statusCode, ConvertHR(hr).c_str()); @@ -631,42 +638,99 @@ int RestCallForEachBlob_Lua(lua_State *L) } Cleanup: - Data()->completedDownloads++; - if (Data()->completedDownloads == Data()->downloadHttpCalls.size()) + Data()->titleStorageCompletedHttpCalls++; + if (Data()->titleStorageCompletedHttpCalls == Data()->titleStorageHttpCalls.size()) { - CallLuaFunctionWithHr(hr, "OnRestCallForEachBlob"); + CallLuaFunctionWithHr(hr, "OnDownloadBlobs"); } LogToScreen("XblHttpCallPerformAsync Completion: hr=%s", ConvertHR(hr).c_str()); }; LogToScreen("Downloading %s", blobMetadata.blobPath.c_str()); - hr = XblHttpCallPerformAsync(output, XblHttpCallResponseBodyType::String, asyncBlock.get()); + hr = XblHttpCallPerformAsync(httpCallHandle, XblHttpCallResponseBodyType::String, asyncBlock.get()); if (SUCCEEDED(hr)) { // The call succeeded, so release the std::unique_ptr ownership of XAsyncBlock* since the callback will take over ownership. // If the call fails, the std::unique_ptr will keep ownership and delete the XAsyncBlock* asyncBlock.release(); } - } return LuaReturnHR(L, hr); } -int RestCallForTMSMetadata_Lua(lua_State *L) +int RestCallToUploadJsonBlob_Lua(lua_State *L) +{ + CreateQueueIfNeeded(); + auto response = Data()->metadataResponseString; + HRESULT hr = S_OK; + + std::string methodName = GetStringFromLua(L, 1, "GET"); + char url[300]; + sprintf_s(url, "https://titlestorage.xboxlive.com/json/users/xuid(%" PRIu64 ")/scids/00000000-0000-0000-0000-000076029b4d/data/apirunner/test/json/file.json,json", Data()->xboxUserId); + + std::string blobContent = GetStringFromLua(L, 1, "{}"); + + // Upload the blob + XblHttpCallHandle httpCallHandle; + hr = XblHttpCallCreate(Data()->xboxLiveContext, "PUT", url, &httpCallHandle); + XblHttpCallRequestSetHeader(httpCallHandle, "Content-Type", "application/json; charset=utf-8", true); + XblHttpCallRequestSetHeader(httpCallHandle, "Accept-Language", "en-US,en", true); + XblHttpCallRequestSetHeader(httpCallHandle, "x-xbl-contract-version", "2", true); + + XblHttpCallRequestSetRequestBodyString(httpCallHandle, blobContent.c_str()); + + Data()->xblHttpCall = httpCallHandle; + + auto asyncBlock = std::make_unique(); + asyncBlock->queue = Data()->queue; + asyncBlock->context = Data()->xblHttpCall; + asyncBlock->callback = [](XAsyncBlock* asyncBlock) + { + std::unique_ptr asyncBlockPtr{ asyncBlock }; // Take over ownership of the XAsyncBlock* + HRESULT hr = XAsyncGetStatus(asyncBlock, false); + + if (SUCCEEDED(hr)) + { + auto httpCall = static_cast(asyncBlock->context); + + uint32_t statusCode; + hr = XblHttpCallGetStatusCode(httpCall, &statusCode); + LogToScreen("Status Code: %d, hr=%s", statusCode, ConvertHR(hr).c_str()); + CHECKHR(hr); + } + + Cleanup: + CallLuaFunctionWithHr(hr, "OnXblTitleStorageRestUpload"); + LogToScreen("XblHttpCallPerformAsync Completion: hr=%s", ConvertHR(hr).c_str()); + }; + + LogToScreen("Uploading %s", url); + hr = XblHttpCallPerformAsync(httpCallHandle, XblHttpCallResponseBodyType::String, asyncBlock.get()); + if (SUCCEEDED(hr)) + { + // The call succeeded, so release the std::unique_ptr ownership of XAsyncBlock* since the callback will take over ownership. + // If the call fails, the std::unique_ptr will keep ownership and delete the XAsyncBlock* + asyncBlock.release(); + } + + return LuaReturnHR(L, hr); +} + +int RestCallForJsonMetadata_Lua(lua_State *L) { CreateQueueIfNeeded(); std::string methodName = GetStringFromLua(L, 1, "GET"); char url[300]; - sprintf_s(url, "https://titlestorage.xboxlive.com/universalplatform/users/xuid(%" PRIu64 ")/scids/00000000-0000-0000-0000-000076029b4d/data?maxItems=100", Data()->xboxUserId); + sprintf_s(url, "https://titlestorage.xboxlive.com/json/users/xuid(%" PRIu64 ")/scids/00000000-0000-0000-0000-000076029b4d/data/apirunner/test/json?maxItems=100", Data()->xboxUserId); - XblHttpCallHandle output; - HRESULT hr = XblHttpCallCreate(Data()->xboxLiveContext, methodName.c_str(), url, &output); - XblHttpCallRequestSetHeader(output, "Content-Type", "application/json; charset=utf-8", true); - XblHttpCallRequestSetHeader(output, "Accept-Language", "en-US,en", true); - XblHttpCallRequestSetHeader(output, "x-xbl-contract-version", "2", true); - Data()->xblHttpCall = output; + XblHttpCallHandle httpCallHandle; + HRESULT hr = XblHttpCallCreate(Data()->xboxLiveContext, methodName.c_str(), url, &httpCallHandle); + XblHttpCallRequestSetHeader(httpCallHandle, "Content-Type", "application/json; charset=utf-8", true); + XblHttpCallRequestSetHeader(httpCallHandle, "Accept-Language", "en-US,en", true); + XblHttpCallRequestSetHeader(httpCallHandle, "x-xbl-contract-version", "2", true); + Data()->xblHttpCall = httpCallHandle; auto asyncBlock = std::make_unique(); asyncBlock->queue = Data()->queue; @@ -681,7 +745,7 @@ int RestCallForTMSMetadata_Lua(lua_State *L) { const char* responseString; hr = XblHttpCallGetResponseString(Data()->xblHttpCall, &responseString); - Data()->responseString = responseString; + Data()->metadataResponseString = responseString; LogToScreen("XblHttpCallResponseGetResponseString: length %d, hr=%s", strlen(responseString), ConvertHR(hr).c_str()); CHECKHR(hr); @@ -694,7 +758,7 @@ int RestCallForTMSMetadata_Lua(lua_State *L) Cleanup: LogToScreen("XblHttpCallPerformAsync Completion: hr=%s", ConvertHR(hr).c_str()); - CallLuaFunctionWithHr(hr, "OnXblTitleStorageRestTMSMetadata"); + CallLuaFunctionWithHr(hr, "OnDownloadMetadataBlobs"); }; hr = XblHttpCallPerformAsync(Data()->xblHttpCall, XblHttpCallResponseBodyType::String, asyncBlock.get()); @@ -708,10 +772,13 @@ int RestCallForTMSMetadata_Lua(lua_State *L) return LuaReturnHR(L, hr); } + + void SetupAPIs_XblTitleStorage() { - lua_register(Data()->L, "RestCallForTMSMetadata", RestCallForTMSMetadata_Lua); - lua_register(Data()->L, "RestCallForEachBlob", RestCallForEachBlob_Lua); + lua_register(Data()->L, "RestCallForJsonMetadata", RestCallForJsonMetadata_Lua); + lua_register(Data()->L, "RestCallToDownloadJsonBlob", RestCallToDownloadJsonBlob_Lua); + lua_register(Data()->L, "RestCallToUploadJsonBlob", RestCallToUploadJsonBlob_Lua); lua_register(Data()->L, "XblTitleStorageBlobMetadataResultGetItems", XblTitleStorageBlobMetadataResultGetItems_Lua); lua_register(Data()->L, "XblTitleStorageBlobMetadataResultHasNext", XblTitleStorageBlobMetadataResultHasNext_Lua); lua_register(Data()->L, "XblTitleStorageBlobMetadataResultGetNextAsync", XblTitleStorageBlobMetadataResultGetNextAsync_Lua); diff --git a/Tests/ApiExplorer/Include/api_explorer.h b/Tests/ApiExplorer/Include/api_explorer.h index e4500358..0dc00de3 100644 --- a/Tests/ApiExplorer/Include/api_explorer.h +++ b/Tests/ApiExplorer/Include/api_explorer.h @@ -144,9 +144,10 @@ struct ApiExplorerData #endif // Title Storage Rest API Calls Data - std::string responseString; - std::vector downloadHttpCalls; - size_t completedDownloads = 0; + std::vector blobResponseStrings; + std::string metadataResponseString; + std::vector titleStorageHttpCalls; + size_t titleStorageCompletedHttpCalls = 0; size_t filesToDownload = 0; // MP diff --git a/Tests/ApiExplorer/Shared/apirunnercloudfns.h b/Tests/ApiExplorer/Shared/apirunnercloudfns.h deleted file mode 100644 index b34dbe53..00000000 --- a/Tests/ApiExplorer/Shared/apirunnercloudfns.h +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#define APIRUNNER_GET_JOINABLE "https://xblapirunnerazurefn20191216050459.azurewebsites.net/api/XblGetJoinable?code=0PsXYMnieKRiJhkhmLg52pmFGY24tCayBgOQm7PFsyxa1ANiOEn7wA==" -#define APIRUNNER_HOST_SESSION "https://xblapirunnerazurefn20191216050459.azurewebsites.net/api/XblHostSession?code=q0d2RbFpaB2h2K3tnC4OUKsaIFwAapXTQ1tSF7wCoMaGMNlEqQSKpg==" -#define APIRUNNER_JOIN_SESSION "https://xblapirunnerazurefn20191216050459.azurewebsites.net/api/XblJoinSession?code=VEhjW85xwYp4RVIS0PXTnatAVjN1pgWshiMokLAaDu8fWRIFaB4aUg==" -#define APIRUNNER_SET_STATE "https://xblapirunnerazurefn20191216050459.azurewebsites.net/api/XblSetState?code=Xmlo7wectT9hUYB6GpT4yXthnbDPO3OweMCtreLkY444ykwgELOQnQ==" -#define APIRUNNER_GET_STATE "https://xblapirunnerazurefn20191216050459.azurewebsites.net/api/XblGetState?code=677yKK5hKpLrH9KekQKHfqsoywL5NmcZ0fZQx7rMeNyeh2pHi1UDvQ==" diff --git a/Tests/ApiExplorer/Shared/pch_common.h b/Tests/ApiExplorer/Shared/pch_common.h index b27d4545..e9ffccfd 100644 --- a/Tests/ApiExplorer/Shared/pch_common.h +++ b/Tests/ApiExplorer/Shared/pch_common.h @@ -51,7 +51,7 @@ #undef RAPIDJSON_NAMESPACE_BEGIN #undef RAPIDJSON_NAMESPACE_END #endif -#include "../External/rapidjson/include/rapidjson/document.h" +#include "../../../External/rapidjson/include/rapidjson/document.h" #include "../Include/multidevice.h" #include "../Include/api_explorer.h" diff --git a/Tests/ApiExplorer/Shared/utils.h b/Tests/ApiExplorer/Shared/utils.h index e0ddf357..b30a852a 100644 --- a/Tests/ApiExplorer/Shared/utils.h +++ b/Tests/ApiExplorer/Shared/utils.h @@ -8,7 +8,7 @@ // internal test-only APIs #define NAMESPACE_MICROSOFT_XBOX_SERVICES_CPP_BEGIN namespace xbox { namespace services { #define NAMESPACE_MICROSOFT_XBOX_SERVICES_CPP_END }} -#include "../Source/Shared/fault_injection.h" +#include "../../../Source/Shared/fault_injection.h" #define VERIFY_IS_TRUE(x, y) if (SUCCEEDED(hr)) { hr = VerifyIsTrue(x, y); } #define ENSURE_IS_TRUE(x, y) if (FAILED(VerifyIsTrue(x, y))) return LuaReturnHR(L, E_ABORT); diff --git a/Tests/ApiExplorer/Tests/notification/achievement_unlock_notification.lua b/Tests/ApiExplorer/Tests/notification/achievement_unlock_notification.lua index 55c110e0..8941871f 100644 --- a/Tests/ApiExplorer/Tests/notification/achievement_unlock_notification.lua +++ b/Tests/ApiExplorer/Tests/notification/achievement_unlock_notification.lua @@ -22,8 +22,12 @@ function achievement_unlock_notification_test() return; end - -- register handler and unlock achievement, returns handler id - local id = RunAchievementUnlock(achievementId); + -- register achievement unlock handler invite handler + local id = XblAchievementUnlockAddNotificationHandler() + XblGameInviteAddNotificationHandler() + + -- unlock achievement + RunAchievementUnlock(achievementId); local status; @@ -38,8 +42,9 @@ function achievement_unlock_notification_test() -- anything other than 0 is an error test.equal(status,0); - -- unregister handler - Cleanup(id); + -- unregister handlers + XblAchievementUnlockRemoveNotificationHandler(id); + XblGameInviteRemoveNotificationHandler() test.stopTest(); end diff --git a/Tests/ApiExplorer/Tests/rta/simpleRTA.lua b/Tests/ApiExplorer/Tests/rta/simpleRTA.lua index a77ce038..f46625e3 100644 --- a/Tests/ApiExplorer/Tests/rta/simpleRTA.lua +++ b/Tests/ApiExplorer/Tests/rta/simpleRTA.lua @@ -11,6 +11,7 @@ end function OnXblRealTimeActivityAddConnectionStateChangeHandler_Connected() print("RTA connection connected"); XblSocialRemoveSocialRelationshipChangedHandler(); + XblContextCloseHandle(); end function OnXblRealTimeActivityAddConnectionStateChangeHandler_Disconnected() diff --git a/Tests/ApiExplorer/Tests/stats2017/stats2017.lua b/Tests/ApiExplorer/Tests/stats2017/stats2017.lua index 36ff3d2d..1c72bdb4 100644 --- a/Tests/ApiExplorer/Tests/stats2017/stats2017.lua +++ b/Tests/ApiExplorer/Tests/stats2017/stats2017.lua @@ -14,9 +14,17 @@ function OnXblTitleManagedStatsDeleteStatsAsync() test.stopTest() end +function OnXblTitleManagedStatsUnableToGetTokenAndSignature() + print("OnXblTitleManagedStatsUnableToGetTokenAndSignature") + test.stopTest() +end + function TitleManagedStats_Handler() - ClearSVD() - XblTitleManagedStatsWriteAsyncWithSVD() + if ClearSVD() == 1 then + OnXblTitleManagedStatsUnableToGetTokenAndSignature() + else + XblTitleManagedStatsWriteAsyncWithSVD() + end end test.TitleManagedStats = function() diff --git a/Tests/ApiExplorer/Tests/titleStorage/title_storage-restCalls.lua b/Tests/ApiExplorer/Tests/titleStorage/title_storage-restCalls.lua index 82b10114..c3c36f1e 100644 --- a/Tests/ApiExplorer/Tests/titleStorage/title_storage-restCalls.lua +++ b/Tests/ApiExplorer/Tests/titleStorage/title_storage-restCalls.lua @@ -3,20 +3,26 @@ common = require 'common' function TitleStorageRestAPI() print("TitleStorage"); - RestCallForTMSMetadata(); + RestCallToUploadJsonBlob( + "{\"difficulty\":1,\"level\":[{\"number\":\"1\",\"quest\":\"swords\"},{\"number\":\"2\",\"quest\":\"iron\"},{\"number\":\"3\",\"quest\":\"gold\"}],\"weapon\":{\"name\":\"poison\",\"timeleft\":\"2mins\"}}" + ); end -function OnXblTitleStorageRestTMSMetadata() - print('Calling RestCallForEachBlob') - RestCallForEachBlob(); +function OnXblTitleStorageRestUpload() + print('Calling RestCallToUploadJsonBlob') + RestCallForJsonMetadata(); end -function OnRestCallForEachBlob() - print('OnRestCallForEachBlob') +function OnDownloadMetadataBlobs() + print('OnUploadBlobs') + RestCallToDownloadJsonBlob(); +end + +function OnDownloadBlobs() + print('OnDownloadBlobs') test.stopTest(); end -test.skip = true test.TitleStorageRestAPI = function() common.init(TitleStorageRestAPI) end \ No newline at end of file diff --git a/Tests/GDK/APIRunner.GDK/APIRunner.GDK.Bin.vcxproj b/Tests/GDK/APIRunner.GDK/APIRunner.GDK.Bin.vcxproj index 339ff3e4..f7368e59 100644 --- a/Tests/GDK/APIRunner.GDK/APIRunner.GDK.Bin.vcxproj +++ b/Tests/GDK/APIRunner.GDK/APIRunner.GDK.Bin.vcxproj @@ -50,7 +50,7 @@ x64 - + Application @@ -383,7 +383,7 @@ - uuid.lib;$(Console_Libs);%(XboxExtensionsDependencies);%(AdditionalDependencies) + uuid.lib;$(Console_Libs);%(XboxExtensionsDependencies);dbghelp.lib;%(AdditionalDependencies) Windows true @@ -514,7 +514,6 @@ - %(Filename)%(Extension) @@ -552,8 +551,6 @@ true - - diff --git a/Tests/GDK/APIRunner.GDK/APIRunner.GDK.Src.vcxproj b/Tests/GDK/APIRunner.GDK/APIRunner.GDK.Src.vcxproj index 73b74858..c4527e34 100644 --- a/Tests/GDK/APIRunner.GDK/APIRunner.GDK.Src.vcxproj +++ b/Tests/GDK/APIRunner.GDK/APIRunner.GDK.Src.vcxproj @@ -560,7 +560,6 @@ true - diff --git a/Tests/UnitTests/Tests/Services/AchievementsTests.cpp b/Tests/UnitTests/Tests/Services/AchievementsTests.cpp index 55a79bd3..7ef39156 100644 --- a/Tests/UnitTests/Tests/Services/AchievementsTests.cpp +++ b/Tests/UnitTests/Tests/Services/AchievementsTests.cpp @@ -512,6 +512,11 @@ public: xbox::services::Vector requirements; }; xbox::services::Vector entries; + + // To keep the strings alive outside the handler for this test + xbox::services::String idContainer{}; + xbox::services::String currentProgressContainer{}; + xbox::services::String targetProgressContainer{}; } context; auto handlerToken = XblAchievementsAddAchievementProgressChangeHandler(xboxLiveContext.get(), @@ -524,10 +529,13 @@ public: const XblAchievementProgressChangeEntry& updateEntry = args->updatedAchievementEntries[entryIndex]; entry.achievementId = updateEntry.achievementId; entry.progressState = updateEntry.progressState; - entry.requirements = xbox::services::Vector( - updateEntry.progression.requirements, - updateEntry.progression.requirements + updateEntry.progression.requirementsCount - ); + entry.requirements = xbox::services::Vector(updateEntry.progression.requirementsCount); + + c->idContainer = updateEntry.progression.requirements[0].id; + c->currentProgressContainer = updateEntry.progression.requirements[0].currentProgressValue; + c->targetProgressContainer = updateEntry.progression.requirements[0].targetProgressValue; + entry.requirements[0] = { c->idContainer.c_str(), c->currentProgressContainer.c_str(), c->targetProgressContainer.c_str() }; + c->entries.push_back(entry); } diff --git a/Utilities/CMake/CMakeLists.txt b/Utilities/CMake/CMakeLists.txt index 77348e8a..3e93e413 100644 --- a/Utilities/CMake/CMakeLists.txt +++ b/Utilities/CMake/CMakeLists.txt @@ -367,11 +367,8 @@ if ( PCWIN32 ) list(APPEND Notification_Source_Files ../../Source/Services/Notification/RTA/notification_service_rta.cpp - ../../Source/Services/Notification/RTA/game_invite_subscription.cpp - ../../Source/Services/Notification/RTA/achievement_unlock_subscription.h - ../../Source/Services/Notification/RTA/achievement_unlock_subscription.cpp - ../../Source/Services/Notification/RTA/spop_kick_subscription.h - ../../Source/Services/Notification/RTA/spop_kick_subscription.cpp + ../../Source/Services/Notification/RTA/notification_subscription.h + ../../Source/Services/Notification/RTA/notification_subscription.cpp ) endif() diff --git a/Utilities/VSOBuildScripts/CopyGDKToGithub.cmd b/Utilities/VSOBuildScripts/CopyGDKToGithub.cmd index 58bd8928..59427252 100644 --- a/Utilities/VSOBuildScripts/CopyGDKToGithub.cmd +++ b/Utilities/VSOBuildScripts/CopyGDKToGithub.cmd @@ -102,6 +102,7 @@ rmdir "%githubPath%Tests\ApiExplorer\UWP" /s /q rmdir "%githubPath%Tests\ApiExplorer\Win" /s /q rmdir "%githubPath%Tests\ApiExplorer\Win32" /s /q rmdir "%githubPath%Tests\ApiExplorer\XDK" /s /q +del "%githubPath%Tests\APIExplorer\Shared\apirunnercloudfns.h" robocopy "%adoPath%Tests\GDK" "%githubPath%Tests\GDK" /s robocopy "%adoPath%Tests\UnitTests" "%githubPath%Tests\UnitTests" /s diff --git a/Utilities/VSOBuildScripts/fetchTools.cmd b/Utilities/VSOBuildScripts/fetchTools.cmd index 34614794..e2f145e3 100644 --- a/Utilities/VSOBuildScripts/fetchTools.cmd +++ b/Utilities/VSOBuildScripts/fetchTools.cmd @@ -7,6 +7,7 @@ set buildToolsBranchArg=%4 cd /D %BUILD_STAGINGDIRECTORY% call git clone https://anything:%patArg%@microsoft.visualstudio.com/DefaultCollection/Xbox.Services/_git/sdk.buildtools cd sdk.buildtools +git reset --hard HEAD if "%buildToolsBranchArg%" NEQ "" call git checkout %buildToolsBranchArg% cd /D %BUILD_STAGINGDIRECTORY% dir "%BUILD_STAGINGDIRECTORY%\sdk.buildtools\buildMachine @@ -17,6 +18,7 @@ goto skipExt cd /D %BUILD_STAGINGDIRECTORY% call git clone https://anything:%patArg%@microsoft.visualstudio.com/Xbox.Services/_git/sdk.external cd sdk.external +git reset --hard HEAD if "%externalBranchArg%" NEQ "" call git checkout %externalBranchArg% cd /D %BUILD_STAGINGDIRECTORY% dir "%BUILD_STAGINGDIRECTORY%\sdk.external\ExtractedGDK\"