Repository: RetroAchievements/RAIntegration
Pull request #1036 (2023-10-23 01:51:27)

don't submit modified rich presence to server


Patch:
diff --git a/rcheevos b/rcheevos
index d24d279e..ce1c16a7 160000
--- a/rcheevos
+++ b/rcheevos
@@ -1,1 +1,1 @@
-Subproject commit d24d279e204ac391ecaa5acb75f4f243c5deeaea
+Subproject commit ce1c16a74b514cfc91229218e0291891a947a7cf
diff --git a/src/data/context/GameContext.cpp b/src/data/context/GameContext.cpp
index eba32bd0..a89b6aa0 100644
--- a/src/data/context/GameContext.cpp
+++ b/src/data/context/GameContext.cpp
@@ -272,7 +272,17 @@ void GameContext::FinishLoadGame(int nResult, const char* sErrorMessage, bool bW
         if (pAsset != nullptr && pAsset->GetChanges() != ra::data::models::AssetChanges::None)
         {
             if (pAsset->IsActive())
+            {
+                if (pAsset->GetType() == ra::data::models::AssetType::RichPresence &&
+                    ra::services::ServiceLocator::Get<ra::ui::viewmodels::WindowManager>().RichPresenceMonitor.IsVisible())
+                {
+                    // if rich presence monitor is open, allow modified rich presence to remain active. otherwise,
+                    // it will be activated when the monitor is opened. it cannot be activated from the list.
+                    continue;
+                }
+
                 pAsset->SetState(ra::data::models::AssetState::Inactive);
+            }
         }
     }
 
diff --git a/src/data/context/SessionTracker.cpp b/src/data/context/SessionTracker.cpp
index 8f3c8ae1..c5ee2cb5 100644
--- a/src/data/context/SessionTracker.cpp
+++ b/src/data/context/SessionTracker.cpp
@@ -210,69 +210,6 @@ std::chrono::seconds SessionTracker::GetTotalPlaytime(unsigned int nGameId) cons
     return tPlaytime;
 }
 
-bool SessionTracker::IsInspectingMemory() const
-{
-    const auto& pWindowManager = ra::services::ServiceLocator::Get<ra::ui::viewmodels::WindowManager>();
-
-    if (pWindowManager.MemoryBookmarks.IsVisible())
-        return true;
-
-    if (pWindowManager.MemoryInspector.IsVisible())
-        return true;
-
-    if (pWindowManager.AssetEditor.IsVisible())
-        return true;
-
-    return false;
-}
-
-std::wstring SessionTracker::GetCurrentActivity() const
-{
-    const auto& pGameContext = ra::services::ServiceLocator::Get<ra::data::context::GameContext>();
-
-    if (IsInspectingMemory())
-    {
-        if (pGameContext.GetMode() == ra::data::context::GameContext::Mode::CompatibilityTest)
-            return L"Testing Compatibility";
-
-        if (_RA_HardcoreModeIsActive())
-            return L"Inspecting Memory in Hardcore mode";
-
-        std::wstring sMessage = L"Inspecting Memory";
-        for (gsl::index i = 0; i < gsl::narrow_cast<gsl::index>(pGameContext.Assets().Count()); ++i)
-        {
-            const auto* pAsset = pGameContext.Assets().GetItemAt(i);
-            if (pAsset)
-            {
-                if (!pAsset->IsShownInList())
-                    continue;
-
-                if (pAsset->GetCategory() == ra::data::models::AssetCategory::Local)
-                {
-                    sMessage = L"Developing Achievements";
-                    break;
-                }
-
-                if (pAsset->GetChanges() != ra::data::models::AssetChanges::None)
-                    sMessage = L"Fixing Achievements";
-            }
-        }
-
-        return sMessage;
-    }
-
-    const auto* pRichPresence = pGameContext.Assets().FindRichPresence();
-    if (pRichPresence && pRichPresence->GetChanges() == ra::data::models::AssetChanges::None)
-    {
-        const auto& pRuntime = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>();
-        const auto sRPResponse = pRuntime.GetRichPresenceDisplayString();
-        if (!sRPResponse.empty())
-            return sRPResponse;
-    }
-
-    return L"Playing " + pGameContext.GameTitle();
-}
-
 } // namespace context
 } // namespace data
 } // namespace ra
diff --git a/src/data/context/SessionTracker.hh b/src/data/context/SessionTracker.hh
index 28e80b62..7e078a72 100644
--- a/src/data/context/SessionTracker.hh
+++ b/src/data/context/SessionTracker.hh
@@ -61,9 +61,6 @@ protected:
 
     void UpdateSession(time_t tSessionStart);
     std::streampos WriteSessionStats(std::chrono::seconds tSessionDuration) const;
-    std::wstring GetCurrentActivity() const;
-
-    bool IsInspectingMemory() const;
 
     std::wstring m_sUsername;
     unsigned int m_nCurrentGameId = 0;
diff --git a/src/services/AchievementRuntime.cpp b/src/services/AchievementRuntime.cpp
index 18d30188..eeaffad4 100644
--- a/src/services/AchievementRuntime.cpp
+++ b/src/services/AchievementRuntime.cpp
@@ -91,12 +91,62 @@ static int CanSubmitLeaderboardEntry(uint32_t nLeaderboardId, rc_client_t*)
     return CanSubmit();
 }
 
+static int RichPresenceOverride(rc_client_t*, char buffer[], size_t buffer_size)
+{
+    const auto& pGameContext = ra::services::ServiceLocator::Get<ra::data::context::GameContext>();
+
+    const auto& pWindowManager = ra::services::ServiceLocator::Get<ra::ui::viewmodels::WindowManager>();
+    const bool bIsInspectingMemory = pWindowManager.MemoryBookmarks.IsVisible() ||
+                                     pWindowManager.MemoryInspector.IsVisible() ||
+                                     pWindowManager.AssetEditor.IsVisible();
+
+    if (bIsInspectingMemory)
+    {
+        if (pGameContext.GetMode() == ra::data::context::GameContext::Mode::CompatibilityTest)
+            return snprintf(buffer, buffer_size, "Testing Compatibility");
+
+        if (_RA_HardcoreModeIsActive())
+            return snprintf(buffer, buffer_size, "Inspecting Memory in Hardcore mode");
+
+        const char* sMessage = "Inspecting Memory";
+        for (gsl::index i = 0; i < gsl::narrow_cast<gsl::index>(pGameContext.Assets().Count()); ++i)
+        {
+            const auto* pAsset = pGameContext.Assets().GetItemAt(i);
+            if (pAsset)
+            {
+                if (!pAsset->IsShownInList())
+                    continue;
+
+                if (pAsset->GetCategory() == ra::data::models::AssetCategory::Local)
+                {
+                    sMessage = "Developing Achievements";
+                    break;
+                }
+
+                if (pAsset->GetChanges() != ra::data::models::AssetChanges::None)
+                    sMessage = "Fixing Achievements";
+            }
+        }
+
+        return snprintf(buffer, buffer_size, "%s", sMessage);
+    }
+
+    // don't send text from modified rich presence to server
+    const auto* pRichPresence = pGameContext.Assets().FindRichPresence();
+    if (pRichPresence && pRichPresence->GetChanges() != ra::data::models::AssetChanges::None)
+        return snprintf(buffer, buffer_size, "Playing %s", ra::Narrow(pGameContext.GameTitle()).c_str());
+
+    // allow default behavior
+    return 0;
+}
+
 AchievementRuntime::AchievementRuntime()
 {
     m_pClient.reset(rc_client_create(AchievementRuntime::ReadMemory, AchievementRuntime::ServerCallAsync));
 
     m_pClient->callbacks.can_submit_achievement_unlock = CanSubmitAchievementUnlock;
     m_pClient->callbacks.can_submit_leaderboard_entry = CanSubmitLeaderboardEntry;
+    m_pClient->callbacks.rich_presence_override = RichPresenceOverride;
 
 #ifndef RA_UTEST
     rc_client_enable_logging(m_pClient.get(), RC_CLIENT_LOG_LEVEL_VERBOSE, AchievementRuntime::LogMessage);
diff --git a/src/ui/viewmodels/AssetListViewModel.cpp b/src/ui/viewmodels/AssetListViewModel.cpp
index ecd79da5..cbbc34e6 100644
--- a/src/ui/viewmodels/AssetListViewModel.cpp
+++ b/src/ui/viewmodels/AssetListViewModel.cpp
@@ -1597,6 +1597,22 @@ void AssetListViewModel::RevertSelected()
 
         if (!sMessage.empty())
             sMessage.push_back(',');
+
+        switch (pAsset->GetType())
+        {
+            case ra::data::models::AssetType::Achievement:
+                sMessage.append("ach");
+                break;
+
+            case ra::data::models::AssetType::Leaderboard:
+                sMessage.append("lbd");
+                break;
+
+            case ra::data::models::AssetType::RichPresence:
+                sMessage.append("rp");
+                break;
+        }
+
         sMessage.append(std::to_string(pAsset->GetID()));
     }
     RA_LOG_INFO("Reverting %zu assets: %s", vAssetsToReset.size(), sMessage);
diff --git a/src/ui/viewmodels/RichPresenceMonitorViewModel.cpp b/src/ui/viewmodels/RichPresenceMonitorViewModel.cpp
index 56e7752a..8d7ad50e 100644
--- a/src/ui/viewmodels/RichPresenceMonitorViewModel.cpp
+++ b/src/ui/viewmodels/RichPresenceMonitorViewModel.cpp
@@ -120,7 +120,14 @@ void RichPresenceMonitorViewModel::OnValueChanged(const BoolModelProperty::Chang
     if (args.Property == IsVisibleProperty)
     {
         if (args.tNewValue)
+        {
+            auto& pGameContext = ra::services::ServiceLocator::GetMutable<ra::data::context::GameContext>();
+            auto* pRichPresence = pGameContext.Assets().FindRichPresence();
+            if (pRichPresence && pRichPresence->GetState() == ra::data::models::AssetState::Inactive)
+                pRichPresence->Activate();
+
             UpdateDisplayString();
+        }
     }
 
     WindowViewModelBase::OnValueChanged(args);


Test patch:
diff --git a/tests/data/context/SessionTracker_Tests.cpp b/tests/data/context/SessionTracker_Tests.cpp
index 4f84f77a..37c546de 100644
--- a/tests/data/context/SessionTracker_Tests.cpp
+++ b/tests/data/context/SessionTracker_Tests.cpp
@@ -10,9 +10,7 @@
 #include "tests\mocks\MockConfiguration.hh"
 #include "tests\mocks\MockGameContext.hh"
 #include "tests\mocks\MockLocalStorage.hh"
-#include "tests\mocks\MockServer.hh"
 #include "tests\mocks\MockThreadPool.hh"
-#include "tests\mocks\MockWindowManager.hh"
 
 using namespace Microsoft::VisualStudio::CppUnitTestFramework;
 
@@ -35,14 +33,13 @@ public:
             mockRuntime.MockGame();
         }
 
-        ra::api::mocks::MockServer mockServer;
         ra::data::context::mocks::MockGameContext mockGameContext;
         ra::services::mocks::MockAchievementRuntime mockRuntime;
         ra::services::mocks::MockClock mockClock;
         ra::services::mocks::MockConfiguration mockConfiguration;
         ra::services::mocks::MockLocalStorage mockStorage;
         ra::services::mocks::MockThreadPool mockThreadPool;
-        ra::ui::viewmodels::mocks::MockWindowManager mockWindowManager;
+
 
         bool HasStoredData() const
         {
@@ -59,16 +56,6 @@ public:
             mockStorage.MockStoredData(StorageItemType::SessionStats, m_sUsernameWide, sContents);
         }
 
-        void MockInspectingMemory(bool bInspectingMemory)
-        {
-            mockWindowManager.MemoryBookmarks.SetIsVisible(bInspectingMemory);
-        }
-
-        std::wstring GetActivity() const
-        {
-            return SessionTracker::GetCurrentActivity();
-        }
-
     private:
         std::string m_sUsername;
         std::wstring m_sUsernameWide;
@@ -226,182 +213,6 @@ public:
         tracker.EndSession();
         Assert::AreEqual(std::string("1234:1534000000:1732:f5\n1234:1534889323:173:bb\n9999:1534889496:169:14\n"), tracker.GetStoredData());
     }
-
-    TEST_METHOD(TestCurrentActivityNoRichPresence)
-    {
-        SessionTrackerHarness tracker;
-        tracker.mockGameContext.SetGameTitle(L"GameTitle");
-        Assert::AreEqual(std::wstring(L"Playing GameTitle"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityRichPresence)
-    {
-        SessionTrackerHarness tracker;
-        tracker.mockGameContext.SetRichPresenceDisplayString(L"Level 10");
-        tracker.mockGameContext.Assets().FindRichPresence()->Activate();
-        Assert::AreEqual(std::wstring(L"Level 10"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityRichPresenceFromFile)
-    {
-        SessionTrackerHarness tracker;
-        tracker.mockGameContext.SetGameTitle(L"GameTitle");
-        tracker.mockGameContext.SetRichPresenceDisplayString(L"Level 10");
-        tracker.mockGameContext.SetRichPresenceFromFile(true);
-        tracker.mockGameContext.Assets().FindRichPresence()->Activate();
-        Assert::AreEqual(std::wstring(L"Playing GameTitle"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityRichPresenceCompatibilityMode)
-    {
-        SessionTrackerHarness tracker;
-        tracker.mockGameContext.SetMode(ra::data::context::GameContext::Mode::CompatibilityTest);
-        tracker.mockGameContext.SetRichPresenceDisplayString(L"Level 10");
-        tracker.mockGameContext.Assets().FindRichPresence()->Activate();
-        Assert::AreEqual(std::wstring(L"Level 10"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryCompatibilityMode)
-    {
-        SessionTrackerHarness tracker;
-        tracker.mockGameContext.Assets().NewAchievement().SetCategory(ra::data::models::AssetCategory::Core);
-        tracker.mockGameContext.SetMode(ra::data::context::GameContext::Mode::CompatibilityTest);
-        tracker.MockInspectingMemory(true);
-        Assert::AreEqual(std::wstring(L"Testing Compatibility"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryNoAchievements)
-    {
-        SessionTrackerHarness tracker;
-        tracker.MockInspectingMemory(true);
-        tracker.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
-        Assert::AreEqual(std::wstring(L"Inspecting Memory"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryNoAchievementsHardcore)
-    {
-        SessionTrackerHarness tracker;
-        tracker.MockInspectingMemory(true);
-        tracker.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, true);
-        Assert::AreEqual(std::wstring(L"Inspecting Memory in Hardcore mode"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryCoreAchievementsHardcore)
-    {
-        SessionTrackerHarness tracker;
-        tracker.MockInspectingMemory(true);
-        tracker.mockGameContext.Assets().NewAchievement().SetCategory(ra::data::models::AssetCategory::Core);
-        tracker.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, true);
-        Assert::AreEqual(std::wstring(L"Inspecting Memory in Hardcore mode"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryModifiedCoreAchievementsHardcore)
-    {
-        SessionTrackerHarness tracker;
-        tracker.MockInspectingMemory(true);
-        auto& pAch = tracker.mockGameContext.Assets().NewAchievement();
-        pAch.SetCategory(ra::data::models::AssetCategory::Core);
-        pAch.SetName(L"Modified");
-        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::Modified);
-        tracker.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, true);
-        Assert::AreEqual(std::wstring(L"Inspecting Memory in Hardcore mode"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryCoreAchievements)
-    {
-        SessionTrackerHarness tracker;
-        tracker.MockInspectingMemory(true);
-        auto& pAch = tracker.mockGameContext.Assets().NewAchievement();
-        pAch.SetCategory(ra::data::models::AssetCategory::Core);
-        pAch.UpdateServerCheckpoint();
-        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::None);
-        tracker.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
-        Assert::AreEqual(std::wstring(L"Inspecting Memory"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryModifiedCoreAchievements)
-    {
-        SessionTrackerHarness tracker;
-        tracker.MockInspectingMemory(true);
-        auto& pAch = tracker.mockGameContext.Assets().NewAchievement();
-        pAch.SetCategory(ra::data::models::AssetCategory::Core);
-        pAch.SetName(L"Modified");
-        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::Modified);
-        tracker.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
-        Assert::AreEqual(std::wstring(L"Fixing Achievements"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryUnpublishedCoreAchievements)
-    {
-        SessionTrackerHarness tracker;
-        tracker.MockInspectingMemory(true);
-        auto& pAch = tracker.mockGameContext.Assets().NewAchievement();
-        pAch.SetCategory(ra::data::models::AssetCategory::Core);
-        pAch.SetName(L"Modified");
-        pAch.UpdateLocalCheckpoint();
-        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::Unpublished);
-        tracker.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
-        Assert::AreEqual(std::wstring(L"Fixing Achievements"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryUnofficialAchievements)
-    {
-        SessionTrackerHarness tracker;
-        tracker.MockInspectingMemory(true);
-        auto& pAch = tracker.mockGameContext.Assets().NewAchievement();
-        pAch.SetCategory(ra::data::models::AssetCategory::Unofficial);
-        pAch.UpdateServerCheckpoint();
-        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::None);
-        tracker.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
-        Assert::AreEqual(std::wstring(L"Inspecting Memory"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryModifiedUnofficialAchievements)
-    {
-        SessionTrackerHarness tracker;
-        tracker.MockInspectingMemory(true);
-        auto& pAch = tracker.mockGameContext.Assets().NewAchievement();
-        pAch.SetCategory(ra::data::models::AssetCategory::Unofficial);
-        pAch.SetName(L"Modified");
-        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::Modified);
-        tracker.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
-        Assert::AreEqual(std::wstring(L"Fixing Achievements"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryLocalAchievements)
-    {
-        SessionTrackerHarness tracker;
-        tracker.MockInspectingMemory(true);
-        tracker.mockGameContext.Assets().NewAchievement().SetCategory(ra::data::models::AssetCategory::Local);
-        tracker.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
-        Assert::AreEqual(std::wstring(L"Developing Achievements"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryLocalAndCoreAchievements)
-    {
-        SessionTrackerHarness tracker;
-        tracker.MockInspectingMemory(true);
-        tracker.mockGameContext.Assets().NewAchievement().SetCategory(ra::data::models::AssetCategory::Local);
-        auto& pAch = tracker.mockGameContext.Assets().NewAchievement();
-        pAch.SetCategory(ra::data::models::AssetCategory::Core);
-        pAch.UpdateServerCheckpoint();
-        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::None);
-        tracker.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
-        Assert::AreEqual(std::wstring(L"Developing Achievements"), tracker.GetActivity());
-    }
-
-    TEST_METHOD(TestCurrentActivityInspectingMemoryLocalAndModifiedCoreAchievements)
-    {
-        SessionTrackerHarness tracker;
-        tracker.MockInspectingMemory(true);
-        tracker.mockGameContext.Assets().NewAchievement().SetCategory(ra::data::models::AssetCategory::Local);
-        auto& pAch = tracker.mockGameContext.Assets().NewAchievement();
-        pAch.SetCategory(ra::data::models::AssetCategory::Core);
-        pAch.SetName(L"Modified");
-        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::Modified);
-        tracker.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
-        Assert::AreEqual(std::wstring(L"Developing Achievements"), tracker.GetActivity());
-    }
 };
 
 } // namespace tests
diff --git a/tests/services/AchievementRuntime_Tests.cpp b/tests/services/AchievementRuntime_Tests.cpp
index 6c7f032d..f72a1bd8 100644
--- a/tests/services/AchievementRuntime_Tests.cpp
+++ b/tests/services/AchievementRuntime_Tests.cpp
@@ -303,6 +303,20 @@ public:
         return LoadProgressFromBuffer(reinterpret_cast<const uint8_t*>(sBuffer.data()));
     }
 
+    std::string GetRichPresenceOverride()
+    {
+        char buffer[256];
+        if (!GetClient()->callbacks.rich_presence_override(GetClient(), buffer, sizeof(buffer)))
+            buffer[0] = '\0';
+
+        return std::string(buffer);
+    }
+
+    void MockInspectingMemory(bool bInspectingMemory)
+    {
+        mockWindowManager.MemoryBookmarks.SetIsVisible(bInspectingMemory);
+    }
+
 private:
     static void CaptureEventHandler(const rc_client_event_t* pEvent, rc_client_t* pClient)
     {
@@ -2916,6 +2930,181 @@ public:
         Assert::IsTrue(bWasReset);
     }
 
+    TEST_METHOD(TestRichPresenceOverrideNoRichPresence)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.mockGameContext.SetGameTitle(L"GameTitle");
+        Assert::AreEqual(std::string(""), runtime.GetRichPresenceOverride()); // no override
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideRichPresence)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.mockGameContext.SetRichPresenceDisplayString(L"Level 10");
+        runtime.mockGameContext.Assets().FindRichPresence()->Activate();
+        Assert::AreEqual(std::string(""), runtime.GetRichPresenceOverride()); // no override
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideRichPresenceFromFile)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.mockGameContext.SetGameTitle(L"GameTitle");
+        runtime.mockGameContext.SetRichPresenceDisplayString(L"Level 10");
+        runtime.mockGameContext.SetRichPresenceFromFile(true);
+        runtime.mockGameContext.Assets().FindRichPresence()->Activate();
+        Assert::AreEqual(std::string("Playing GameTitle"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideRichPresenceCompatibilityMode)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.mockGameContext.SetMode(ra::data::context::GameContext::Mode::CompatibilityTest);
+        runtime.mockGameContext.SetRichPresenceDisplayString(L"Level 10");
+        runtime.mockGameContext.Assets().FindRichPresence()->Activate();
+        Assert::AreEqual(std::string(""), runtime.GetRichPresenceOverride()); // no override
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryCompatibilityMode)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.mockGameContext.Assets().NewAchievement().SetCategory(ra::data::models::AssetCategory::Core);
+        runtime.mockGameContext.SetMode(ra::data::context::GameContext::Mode::CompatibilityTest);
+        runtime.MockInspectingMemory(true);
+        Assert::AreEqual(std::string("Testing Compatibility"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryNoAchievements)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.MockInspectingMemory(true);
+        runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
+        Assert::AreEqual(std::string("Inspecting Memory"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryNoAchievementsHardcore)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.MockInspectingMemory(true);
+        runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, true);
+        Assert::AreEqual(std::string("Inspecting Memory in Hardcore mode"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryCoreAchievementsHardcore)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.MockInspectingMemory(true);
+        runtime.mockGameContext.Assets().NewAchievement().SetCategory(ra::data::models::AssetCategory::Core);
+        runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, true);
+        Assert::AreEqual(std::string("Inspecting Memory in Hardcore mode"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryModifiedCoreAchievementsHardcore)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.MockInspectingMemory(true);
+        auto& pAch = runtime.mockGameContext.Assets().NewAchievement();
+        pAch.SetCategory(ra::data::models::AssetCategory::Core);
+        pAch.SetName(L"Modified");
+        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::Modified);
+        runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, true);
+        Assert::AreEqual(std::string("Inspecting Memory in Hardcore mode"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryCoreAchievements)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.MockInspectingMemory(true);
+        auto& pAch = runtime.mockGameContext.Assets().NewAchievement();
+        pAch.SetCategory(ra::data::models::AssetCategory::Core);
+        pAch.UpdateServerCheckpoint();
+        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::None);
+        runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
+        Assert::AreEqual(std::string("Inspecting Memory"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryModifiedCoreAchievements)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.MockInspectingMemory(true);
+        auto& pAch = runtime.mockGameContext.Assets().NewAchievement();
+        pAch.SetCategory(ra::data::models::AssetCategory::Core);
+        pAch.SetName(L"Modified");
+        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::Modified);
+        runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
+        Assert::AreEqual(std::string("Fixing Achievements"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryUnpublishedCoreAchievements)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.MockInspectingMemory(true);
+        auto& pAch = runtime.mockGameContext.Assets().NewAchievement();
+        pAch.SetCategory(ra::data::models::AssetCategory::Core);
+        pAch.SetName(L"Modified");
+        pAch.UpdateLocalCheckpoint();
+        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::Unpublished);
+        runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
+        Assert::AreEqual(std::string("Fixing Achievements"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryUnofficialAchievements)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.MockInspectingMemory(true);
+        auto& pAch = runtime.mockGameContext.Assets().NewAchievement();
+        pAch.SetCategory(ra::data::models::AssetCategory::Unofficial);
+        pAch.UpdateServerCheckpoint();
+        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::None);
+        runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
+        Assert::AreEqual(std::string("Inspecting Memory"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryModifiedUnofficialAchievements)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.MockInspectingMemory(true);
+        auto& pAch = runtime.mockGameContext.Assets().NewAchievement();
+        pAch.SetCategory(ra::data::models::AssetCategory::Unofficial);
+        pAch.SetName(L"Modified");
+        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::Modified);
+        runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
+        Assert::AreEqual(std::string("Fixing Achievements"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryLocalAchievements)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.MockInspectingMemory(true);
+        runtime.mockGameContext.Assets().NewAchievement().SetCategory(ra::data::models::AssetCategory::Local);
+        runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
+        Assert::AreEqual(std::string("Developing Achievements"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryLocalAndCoreAchievements)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.MockInspectingMemory(true);
+        runtime.mockGameContext.Assets().NewAchievement().SetCategory(ra::data::models::AssetCategory::Local);
+        auto& pAch = runtime.mockGameContext.Assets().NewAchievement();
+        pAch.SetCategory(ra::data::models::AssetCategory::Core);
+        pAch.UpdateServerCheckpoint();
+        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::None);
+        runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
+        Assert::AreEqual(std::string("Developing Achievements"), runtime.GetRichPresenceOverride());
+    }
+
+    TEST_METHOD(TestRichPresenceOverrideInspectingMemoryLocalAndModifiedCoreAchievements)
+    {
+        AchievementRuntimeHarness runtime;
+        runtime.MockInspectingMemory(true);
+        runtime.mockGameContext.Assets().NewAchievement().SetCategory(ra::data::models::AssetCategory::Local);
+        auto& pAch = runtime.mockGameContext.Assets().NewAchievement();
+        pAch.SetCategory(ra::data::models::AssetCategory::Core);
+        pAch.SetName(L"Modified");
+        Assert::AreEqual(pAch.GetChanges(), ra::data::models::AssetChanges::Modified);
+        runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false);
+        Assert::AreEqual(std::string("Developing Achievements"), runtime.GetRichPresenceOverride());
+    }
 };
 
 } // namespace tests