// 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/Config/Config.h" #include "Common/HookableEvent.h" #include "Common/Lazy.h" #include "Common/WorkQueueThread.h" #include "DiscIO/Volume.h" #include "VideoCommon/Assets/CustomTextureData.h" #ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION #include #endif // RC_CLIENT_SUPPORTS_RAINTEGRATION namespace Core { 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"; 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; }; Common::HookableEvent update_event; Common::HookableEvent login_event; static AchievementManager& GetInstance(); void Init(void* hwnd); void Login(const std::string& password); bool HasAPIToken() const; void LoadGame(const DiscIO::Volume* volume); void ChangeDisc(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 FilterApprovedPatches(std::vector& patches, std::string_view game_id, u16 revision) const; void FilterApprovedGeckoCodes(std::vector& codes, std::string_view game_id, u16 revision) const; void FilterApprovedARCodes(std::vector& codes, std::string_view game_id, u16 revision) const; bool ShouldGeckoCodeBeActivated(const Gecko::GeckoCode& code, std::string_view game_id, u16 revision) const; bool ShouldARCodeBeActivated(const ActionReplay::ARCode& code, std::string_view game_id, u16 revision) const; bool IsApprovedGeckoCode(const Gecko::GeckoCode& code, std::string_view game_id, u16 revision) const; bool IsApprovedARCode(const ActionReplay::ARCode& code, std::string_view game_id, u16 revision) const; void SetSpectatorMode(); std::string_view GetPlayerDisplayName() const; u32 GetPlayerScore() const; const Badge& GetPlayerBadge() const; std::string_view GetGameDisplayName() const; rc_client_t* GetClient(); 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; #ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION Common::HookableEvent<> dev_menu_update_event; const rc_client_raintegration_menu_t* GetDevelopmentMenu(); u32 ActivateDevMenuItem(u32 menu_item_id); bool CheckForModifications() { return rc_client_raintegration_has_modifications(m_client); } #endif // RC_CLIENT_SUPPORTS_RAINTEGRATION 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* FilereaderOpen(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); static u32 FindConsoleID(const DiscIO::Platform& platform); 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, std::string_view game_id, u16 revision) const; template bool ShouldCodeBeActivated(const T& code, std::string_view game_id, u16 revision) const; template bool IsApprovedCode(const T& code, std::string_view game_id, u16 revision) 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 MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client); void FetchBadge(Badge* badge, u32 badge_type, BadgeNameFunction function, UpdatedItems callback_data); static void EventHandler(const rc_client_event_t* event, rc_client_t* client); #ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION static void LoadIntegrationCallback(int result, const char* error_message, rc_client_t* client, void* userdata); static void RAIntegrationEventHandler(const rc_client_raintegration_event_t* event, rc_client_t* client); static void MemoryPoker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client); static void GameTitleEstimateHandler(char* buffer, u32 buffer_size, rc_client_t* client); #endif // RC_CLIENT_SUPPORTS_RAINTEGRATION rc_client_t* m_client{}; std::atomic m_system{}; std::unique_ptr m_loading_volume; Config::ConfigChangedCallbackID m_config_changed_callback_id; 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; 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::unordered_map m_leaderboard_map; bool m_challenges_updated = false; std::unordered_set m_active_challenges; std::vector m_active_leaderboards; bool m_dll_found = false; #ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION std::string m_title_estimate; #endif // RC_CLIENT_SUPPORTS_RAINTEGRATION Common::AsyncWorkThread m_queue; Common::AsyncWorkThread m_image_queue; mutable std::recursive_mutex m_lock; std::recursive_mutex m_filereader_lock; }; // class AchievementManager #else // USE_RETRO_ACHIEVEMENTS #include #include "Common/CommonTypes.h" #include "Core/ActionReplay.h" #include "Core/GeckoCode.h" namespace DiscIO { class Volume; } class AchievementManager { public: static AchievementManager& GetInstance() { static AchievementManager s_instance; return s_instance; } constexpr bool IsHardcoreModeActive() { return false; } constexpr bool ShouldGeckoCodeBeActivated(const Gecko::GeckoCode& code, std::string_view game_id, u16 revision) { return code.enabled; } constexpr bool ShouldARCodeBeActivated(const ActionReplay::ARCode& code, std::string_view game_id, u16 revision) { return code.enabled; } constexpr void LoadGame(const DiscIO::Volume*) {} constexpr void ChangeDisc(const DiscIO::Volume*) {} constexpr void SetBackgroundExecutionAllowed(bool allowed) {} constexpr void DoFrame() {} constexpr void CloseGame() {} }; #endif // USE_RETRO_ACHIEVEMENTS