From 6c5203196644ec803205952e4c80b63f0efd6a7b Mon Sep 17 00:00:00 2001
From: Silent <zdanio95@gmail.com>
Date: Wed, 9 Jun 2021 20:10:18 +0200
Subject: [PATCH] Add an "Unofficial Test Mode" to allow unlocking non-core
 achievements

---
 .../achievementsettingswidget.cpp             |  6 +++++
 .../achievementsettingswidget.ui              |  7 +++++
 src/frontend-common/cheevos.cpp               | 26 +++++++++++++++----
 src/frontend-common/cheevos.h                 |  5 +++-
 src/frontend-common/common_host_interface.cpp |  5 +++-
 src/frontend-common/fullscreen_ui.cpp         |  5 ++++
 6 files changed, 47 insertions(+), 7 deletions(-)

diff --git a/src/duckstation-qt/achievementsettingswidget.cpp b/src/duckstation-qt/achievementsettingswidget.cpp
index c2a62bb2b..9fa4dcf1f 100644
--- a/src/duckstation-qt/achievementsettingswidget.cpp
+++ b/src/duckstation-qt/achievementsettingswidget.cpp
@@ -18,6 +18,8 @@ AchievementSettingsWidget::AchievementSettingsWidget(QtHostInterface* host_inter
 
   SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.richPresence, "Cheevos", "RichPresence", true);
   SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.testMode, "Cheevos", "TestMode", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.unofficialTestMode, "Cheevos",
+                                               "UnofficialTestMode", false);
   SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.useFirstDiscFromPlaylist, "Cheevos",
                                                "UseFirstDiscFromPlaylist", true);
   m_ui.enable->setChecked(m_host_interface->GetBoolSettingValue("Cheevos", "Enabled", false));
@@ -28,6 +30,10 @@ AchievementSettingsWidget::AchievementSettingsWidget(QtHostInterface* host_inter
   dialog->registerWidgetHelp(m_ui.testMode, tr("Enable Test Mode"), tr("Unchecked"),
                              tr("When enabled, DuckStation will assume all achievements are locked and not send any "
                                 "unlock notifications to the server."));
+  dialog->registerWidgetHelp(
+    m_ui.unofficialTestMode, tr("Test Unofficial Achievements"), tr("Unchecked"),
+    tr("When enabled, DuckStation will list achievements from unofficial sets. Please note that these achievements are "
+       "not tracked by RetroAchievements, so they unlock every time."));
   dialog->registerWidgetHelp(
     m_ui.richPresence, tr("Enable Rich Presence"), tr("Unchecked"),
     tr("When enabled, rich presence information will be collected and sent to the server where supported."));
diff --git a/src/duckstation-qt/achievementsettingswidget.ui b/src/duckstation-qt/achievementsettingswidget.ui
index a965c662a..83c646427 100644
--- a/src/duckstation-qt/achievementsettingswidget.ui
+++ b/src/duckstation-qt/achievementsettingswidget.ui
@@ -67,6 +67,13 @@
         </property>
        </widget>
       </item>
+      <item row="2" column="1">
+       <widget class="QCheckBox" name="unofficialTestMode">
+        <property name="text">
+         <string>Test Unofficial Achievements</string>
+        </property>
+       </widget>
+      </item>
      </layout>
     </widget>
    </item>
diff --git a/src/frontend-common/cheevos.cpp b/src/frontend-common/cheevos.cpp
index b0a2ae85b..571c87abf 100644
--- a/src/frontend-common/cheevos.cpp
+++ b/src/frontend-common/cheevos.cpp
@@ -55,6 +55,7 @@ u32 g_game_id = 0;
 
 static bool s_logged_in = false;
 static bool s_test_mode = false;
+static bool s_unofficial_test_mode = false;
 static bool s_use_first_disc_from_playlist = true;
 static bool s_rich_presence_enabled = false;
 
@@ -212,7 +213,8 @@ static std::string GetUserAgent()
   return StringUtil::StdStringFromFormat("DuckStation for %s (%s) %s", SYSTEM_STR, CPU_ARCH_STR, g_scm_tag_str);
 }
 
-bool Initialize(bool test_mode, bool use_first_disc_from_playlist, bool enable_rich_presence, bool challenge_mode)
+bool Initialize(bool test_mode, bool use_first_disc_from_playlist, bool enable_rich_presence, bool challenge_mode,
+                bool include_unofficial)
 {
   s_http_downloader = FrontendCommon::HTTPDownloader::Create(GetUserAgent().c_str());
   if (!s_http_downloader)
@@ -224,6 +226,7 @@ bool Initialize(bool test_mode, bool use_first_disc_from_playlist, bool enable_r
   g_active = true;
   g_challenge_mode = challenge_mode;
   s_test_mode = test_mode;
+  s_unofficial_test_mode = include_unofficial;
   s_use_first_disc_from_playlist = use_first_disc_from_playlist;
   s_rich_presence_enabled = enable_rich_presence;
   rc_runtime_init(&s_rcheevos_runtime);
@@ -298,6 +301,11 @@ bool IsTestModeActive()
   return s_test_mode;
 }
 
+bool IsUnofficialTestModeActive()
+{
+  return s_unofficial_test_mode;
+}
+
 bool IsUsingFirstDiscFromPlaylist()
 {
   return s_use_first_disc_from_playlist;
@@ -672,16 +680,16 @@ static void GetPatchesCallback(s32 status_code, const FrontendCommon::HTTPDownlo
       }
 
       const u32 id = achievement["ID"].GetUint();
-      const u32 category = achievement["Flags"].GetUint();
+      const AchievementCategory category = static_cast<AchievementCategory>(achievement["Flags"].GetUint());
       const char* memaddr = achievement["MemAddr"].GetString();
       std::string title = achievement["Title"].GetString();
       std::string description = GetOptionalString(achievement, "Description");
       std::string badge_name = GetOptionalString(achievement, "BadgeName");
       const u32 points = GetOptionalUInt(achievement, "Points");
 
-      // Skip local and unofficial achievements for now.
-      if (static_cast<AchievementCategory>(category) == AchievementCategory::Local ||
-          static_cast<AchievementCategory>(category) == AchievementCategory::Unofficial)
+      // Skip local and unofficial achievements for now, unless "Test Unofficial Achievements" is enabled
+      if (!s_unofficial_test_mode &&
+          (category == AchievementCategory::Local || category == AchievementCategory::Unofficial))
       {
         Log_WarningPrintf("Skipping unofficial achievement %u (%s)", id, title.c_str());
         continue;
@@ -701,6 +709,7 @@ static void GetPatchesCallback(s32 status_code, const FrontendCommon::HTTPDownlo
       cheevo.locked = true;
       cheevo.active = false;
       cheevo.points = points;
+      cheevo.category = category;
 
       if (!badge_name.empty())
       {
@@ -1101,6 +1110,13 @@ void UnlockAchievement(u32 achievement_id, bool add_notification /* = true*/)
     return;
   }
 
+  if (achievement->category != AchievementCategory::Core)
+  {
+    Log_WarningPrintf("Skipping sending achievement %u unlock to server because it's not from the core set.",
+                      achievement_id);
+    return;
+  }
+
   char url[512];
   rc_url_award_cheevo(url, sizeof(url), s_username.c_str(), s_login_token.c_str(), achievement_id,
                       static_cast<int>(g_challenge_mode), s_game_hash.c_str());
diff --git a/src/frontend-common/cheevos.h b/src/frontend-common/cheevos.h
index d6c108f24..39231a8cb 100644
--- a/src/frontend-common/cheevos.h
+++ b/src/frontend-common/cheevos.h
@@ -23,6 +23,7 @@ struct Achievement
   std::string locked_badge_path;
   std::string unlocked_badge_path;
   u32 points;
+  AchievementCategory category;
   bool locked;
   bool active;
 };
@@ -56,13 +57,15 @@ ALWAYS_INLINE u32 GetGameID()
   return g_game_id;
 }
 
-bool Initialize(bool test_mode, bool use_first_disc_from_playlist, bool enable_rich_presence, bool challenge_mode);
+bool Initialize(bool test_mode, bool use_first_disc_from_playlist, bool enable_rich_presence, bool challenge_mode,
+                bool include_unofficial);
 void Reset();
 void Shutdown();
 void Update();
 
 bool IsLoggedIn();
 bool IsTestModeActive();
+bool IsUnofficialTestModeActive();
 bool IsUsingFirstDiscFromPlaylist();
 bool IsRichPresenceEnabled();
 const std::string& GetUsername();
diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp
index a6c47140f..f0e178346 100644
--- a/src/frontend-common/common_host_interface.cpp
+++ b/src/frontend-common/common_host_interface.cpp
@@ -2807,6 +2807,7 @@ void CommonHostInterface::SetDefaultSettings(SettingsInterface& si)
 #ifdef WITH_CHEEVOS
   si.SetBoolValue("Cheevos", "Enabled", false);
   si.SetBoolValue("Cheevos", "TestMode", false);
+  si.SetBoolValue("Cheevos", "UnofficialTestMode", false);
   si.SetBoolValue("Cheevos", "UseFirstDiscFromPlaylist", true);
   si.DeleteValue("Cheevos", "Username");
   si.DeleteValue("Cheevos", "Token");
@@ -3818,11 +3819,13 @@ void CommonHostInterface::UpdateCheevosActive()
 {
   const bool cheevos_enabled = GetBoolSettingValue("Cheevos", "Enabled", false);
   const bool cheevos_test_mode = GetBoolSettingValue("Cheevos", "TestMode", false);
+  const bool cheevos_unofficial_test_mode = GetBoolSettingValue("Cheevos", "UnofficialTestMode", false);
   const bool cheevos_use_first_disc_from_playlist = GetBoolSettingValue("Cheevos", "UseFirstDiscFromPlaylist", true);
   const bool cheevos_rich_presence = GetBoolSettingValue("Cheevos", "RichPresence", true);
   const bool cheevos_hardcore = GetBoolSettingValue("Cheevos", "ChallengeMode", false);
 
   if (cheevos_enabled != Cheevos::IsActive() || cheevos_test_mode != Cheevos::IsTestModeActive() ||
+      cheevos_unofficial_test_mode != Cheevos::IsUnofficialTestModeActive() ||
       cheevos_use_first_disc_from_playlist != Cheevos::IsUsingFirstDiscFromPlaylist() ||
       cheevos_rich_presence != Cheevos::IsRichPresenceEnabled() ||
       cheevos_hardcore != Cheevos::IsChallengeModeEnabled())
@@ -3831,7 +3834,7 @@ void CommonHostInterface::UpdateCheevosActive()
     if (cheevos_enabled)
     {
       if (!Cheevos::Initialize(cheevos_test_mode, cheevos_use_first_disc_from_playlist, cheevos_rich_presence,
-                               cheevos_hardcore))
+                               cheevos_hardcore, cheevos_unofficial_test_mode))
         ReportError("Failed to initialize cheevos after settings change.");
     }
   }
diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp
index 7ad0abd42..68b323ba9 100644
--- a/src/frontend-common/fullscreen_ui.cpp
+++ b/src/frontend-common/fullscreen_ui.cpp
@@ -2280,6 +2280,11 @@ void DrawSettingsWindow()
                                     "When enabled, DuckStation will assume all achievements are locked and not "
                                     "send any unlock notifications to the server.",
                                     "Cheevos", "TestMode", false);
+        settings_changed |=
+          ToggleButtonForNonSetting(ICON_FA_MEDAL "  Test Unofficial Achievements",
+                                    "When enabled, DuckStation will list achievements from unofficial sets. These "
+                                    "achievements are not tracked by RetroAchievements.",
+                                    "Cheevos", "UnofficialTestMode", false);
         settings_changed |= ToggleButtonForNonSetting(ICON_FA_COMPACT_DISC "  Use First Disc From Playlist",
                                                       "When enabled, the first disc in a playlist will be used for "
                                                       "achievements, regardless of which disc is active.",