// Copyright 2023 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #ifdef USE_RETRO_ACHIEVEMENTS #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Common/CommonTypes.h" #include "Common/Event.h" #include "Common/HttpRequest.h" #include "Common/JsonUtil.h" #include "Common/Lazy.h" #include "Common/WorkQueueThread.h" #include "DiscIO/Volume.h" #include "VideoCommon/Assets/CustomTextureData.h" namespace Core { class CPUThreadGuard; class System; } // namespace Core namespace PatchEngine { struct Patch; } // namespace PatchEngine namespace Gecko { class GeckoCode; } // namespace Gecko namespace ActionReplay { struct ARCode; } // namespace ActionReplay class AchievementManager { public: using BadgeNameFunction = std::function; static constexpr size_t HASH_SIZE = 33; using Hash = std::array; using AchievementId = u32; static constexpr size_t FORMAT_SIZE = 24; using FormattedValue = std::array; using LeaderboardRank = u32; static constexpr size_t RP_SIZE = 256; using RichPresence = std::array; using Badge = VideoCommon::CustomTextureData::ArraySlice::Level; static constexpr size_t MAX_DISPLAYED_LBOARDS = 4; static constexpr std::string_view DEFAULT_PLAYER_BADGE_FILENAME = "achievements_player.png"; static constexpr std::string_view DEFAULT_GAME_BADGE_FILENAME = "achievements_game.png"; static constexpr std::string_view DEFAULT_LOCKED_BADGE_FILENAME = "achievements_locked.png"; static constexpr std::string_view DEFAULT_UNLOCKED_BADGE_FILENAME = "achievements_unlocked.png"; static constexpr std::string_view GRAY = "transparent"; static constexpr std::string_view GOLD = "#FFD700"; static constexpr std::string_view BLUE = "#0B71C1"; static constexpr std::string_view APPROVED_LIST_FILENAME = "ApprovedInis.json"; static const inline Common::SHA1::Digest APPROVED_LIST_HASH = { 0x4F, 0x45, 0xB7, 0xA3, 0xC4, 0x6E, 0xAF, 0x80, 0x58, 0xA5, 0x53, 0x99, 0xF8, 0x05, 0xC3, 0x83, 0x22, 0xA4, 0x5F, 0x65}; struct LeaderboardEntry { std::string username; FormattedValue score; LeaderboardRank rank; }; struct LeaderboardStatus { std::string name; std::string description; u32 player_index = 0; std::unordered_map entries; }; struct UpdatedItems { bool all = false; bool player_icon = false; bool game_icon = false; bool all_achievements = false; std::set achievements{}; bool all_leaderboards = false; std::set leaderboards{}; bool rich_presence = false; int failed_login_code = 0; }; using UpdateCallback = std::function; static AchievementManager& GetInstance(); void Init(); void SetUpdateCallback(UpdateCallback callback); void Login(const std::string& password); bool HasAPIToken() const; void LoadGame(const std::string& file_path, const DiscIO::Volume* volume); bool IsGameLoaded() const; void SetBackgroundExecutionAllowed(bool allowed); static std::string CalculateHash(const std::string& file_path); void FetchPlayerBadge(); void FetchGameBadges(); void DoFrame(); bool CanPause(); void DoIdle(); std::recursive_mutex& GetLock(); bool IsHardcoreModeActive() const; void SetGameIniId(const std::string& game_ini_id) { m_game_ini_id = game_ini_id; } void FilterApprovedPatches(std::vector& patches, const std::string& game_ini_id) const; void FilterApprovedGeckoCodes(std::vector& codes, const std::string& game_ini_id) const; void FilterApprovedARCodes(std::vector& codes, const std::string& game_ini_id) const; bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_ini_id) const; bool CheckApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_ini_id) const; void SetSpectatorMode(); std::string_view GetPlayerDisplayName() const; u32 GetPlayerScore() const; const Badge& GetPlayerBadge() const; std::string_view GetGameDisplayName() const; rc_client_t* GetClient(); rc_api_fetch_game_data_response_t* GetGameData(); const Badge& GetGameBadge() const; const Badge& GetAchievementBadge(AchievementId id, bool locked) const; const LeaderboardStatus* GetLeaderboardInfo(AchievementId leaderboard_id); RichPresence GetRichPresence() const; bool AreChallengesUpdated() const; void ResetChallengesUpdated(); const std::unordered_set& GetActiveChallenges() const; std::vector GetActiveLeaderboards() const; void DoState(PointerWrap& p); void CloseGame(); void Logout(); void Shutdown(); private: AchievementManager() = default; struct FilereaderState { int64_t position = 0; std::unique_ptr volume; }; static picojson::value LoadApprovedList(); static void* FilereaderOpenByFilepath(const char* path_utf8); static void* FilereaderOpenByVolume(const char* path_utf8); static void FilereaderSeek(void* file_handle, int64_t offset, int origin); static int64_t FilereaderTell(void* file_handle); static size_t FilereaderRead(void* file_handle, void* buffer, size_t requested_bytes); static void FilereaderClose(void* file_handle); void LoadDefaultBadges(); static void LoginCallback(int result, const char* error_message, rc_client_t* client, void* userdata); void FetchBoardInfo(AchievementId leaderboard_id); std::unique_ptr& GetLoadingVolume() { return m_loading_volume; } static void LoadGameCallback(int result, const char* error_message, rc_client_t* client, void* userdata); static void ChangeMediaCallback(int result, const char* error_message, rc_client_t* client, void* userdata); void DisplayWelcomeMessage(); void SetHardcoreMode(); template void FilterApprovedIni(std::vector& codes, const std::string& game_ini_id) const; template bool CheckApprovedCode(const T& code, const std::string& game_ini_id) const; Common::SHA1::Digest GetCodeHash(const PatchEngine::Patch& patch) const; Common::SHA1::Digest GetCodeHash(const Gecko::GeckoCode& code) const; Common::SHA1::Digest GetCodeHash(const ActionReplay::ARCode& code) const; static void LeaderboardEntriesCallback(int result, const char* error_message, rc_client_leaderboard_entry_list_t* list, rc_client_t* client, void* userdata); static void HandleAchievementTriggeredEvent(const rc_client_event_t* client_event); static void HandleLeaderboardStartedEvent(const rc_client_event_t* client_event); static void HandleLeaderboardFailedEvent(const rc_client_event_t* client_event); static void HandleLeaderboardSubmittedEvent(const rc_client_event_t* client_event); static void HandleLeaderboardTrackerUpdateEvent(const rc_client_event_t* client_event); static void HandleLeaderboardTrackerShowEvent(const rc_client_event_t* client_event); static void HandleLeaderboardTrackerHideEvent(const rc_client_event_t* client_event); static void HandleAchievementChallengeIndicatorShowEvent(const rc_client_event_t* client_event); static void HandleAchievementChallengeIndicatorHideEvent(const rc_client_event_t* client_event); static void HandleAchievementProgressIndicatorShowEvent(const rc_client_event_t* client_event); static void HandleGameCompletedEvent(const rc_client_event_t* client_event, rc_client_t* client); static void HandleResetEvent(const rc_client_event_t* client_event); static void HandleServerErrorEvent(const rc_client_event_t* client_event); static void Request(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); static u32 MemoryVerifier(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client); static u32 MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client); void FetchBadge(Badge* badge, u32 badge_type, const BadgeNameFunction function, const UpdatedItems callback_data); static void EventHandler(const rc_client_event_t* event, rc_client_t* client); rc_runtime_t m_runtime{}; rc_client_t* m_client{}; std::atomic m_system{}; bool m_is_runtime_initialized = false; UpdateCallback m_update_callback = [](const UpdatedItems&) {}; std::unique_ptr m_loading_volume; Badge m_default_player_badge; Badge m_default_game_badge; Badge m_default_unlocked_badge; Badge m_default_locked_badge; std::atomic_bool m_background_execution_allowed = true; Badge m_player_badge; Hash m_game_hash{}; u32 m_game_id = 0; rc_api_fetch_game_data_response_t m_game_data{}; bool m_is_game_loaded = false; Badge m_game_badge; bool m_display_welcome_message = false; std::unordered_map m_unlocked_badges; std::unordered_map m_locked_badges; RichPresence m_rich_presence; std::chrono::steady_clock::time_point m_last_rp_time = std::chrono::steady_clock::now(); std::chrono::steady_clock::time_point m_last_progress_message = std::chrono::steady_clock::now(); Common::Lazy m_ini_root{LoadApprovedList}; std::string m_game_ini_id; std::unordered_map m_leaderboard_map; bool m_challenges_updated = false; std::unordered_set m_active_challenges; std::vector m_active_leaderboards; Common::WorkQueueThread> m_queue; Common::WorkQueueThread> m_image_queue; mutable std::recursive_mutex m_lock; std::recursive_mutex m_filereader_lock; }; // class AchievementManager #else // USE_RETRO_ACHIEVEMENTS #include namespace ActionReplay { struct ARCode; } namespace DiscIO { class Volume; } namespace Gecko { class GeckoCode; } class AchievementManager { public: static AchievementManager& GetInstance() { static AchievementManager s_instance; return s_instance; } constexpr bool IsHardcoreModeActive() { return false; } constexpr bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_ini_id) { return true; }; constexpr bool CheckApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_ini_id) { return true; }; constexpr void LoadGame(const std::string&, const DiscIO::Volume*) {} constexpr void SetBackgroundExecutionAllowed(bool allowed) {} constexpr void DoFrame() {} constexpr void CloseGame() {} }; #endif // USE_RETRO_ACHIEVEMENTS