From 0c14b0c8a70799072ddccb648006914af7c62855 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Fri, 21 Jun 2024 23:44:21 -0400 Subject: Add Approved Patch Allowlist for Achievements Prototype of a system to whitelist known game patches that are allowed to be used while RetroAchievements Hardcore mode is active. ApprovedInis.txt contains known hashes for the ini files as they appear in the repo, and can be compared to the local versions of these files to ensure they have not been edited locally by the player. ApprovedInis.txt is hashed and verified similarly first, with its hash residing as a const string within AchievementManager.h, ensuring ApprovedInis and the hashes within cannot be modified without editing Dolphin's source code and recompiling completely. --- Source/Core/Core/AchievementManager.cpp | 72 +++++++++++++++++++++++++++++++++ Source/Core/Core/AchievementManager.h | 20 ++++++++- Source/Core/Core/PatchEngine.cpp | 10 +++-- 3 files changed, 98 insertions(+), 4 deletions(-) (limited to 'Source/Core') diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 7d7c0af374..1920ad0552 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -14,6 +14,7 @@ #include #include "Common/Assert.h" +#include "Common/BitUtils.h" #include "Common/CommonPaths.h" #include "Common/FileUtil.h" #include "Common/IOFile.h" @@ -26,6 +27,7 @@ #include "Core/Core.h" #include "Core/HW/Memmap.h" #include "Core/HW/VideoInterface.h" +#include "Core/PatchEngine.h" #include "Core/PowerPC/MMU.h" #include "Core/System.h" #include "DiscIO/Blob.h" @@ -70,6 +72,34 @@ void AchievementManager::Init() } } +void AchievementManager::LoadApprovedList() +{ + picojson::value temp; + std::string error; + if (!JsonFromFile(fmt::format("{}{}{}", File::GetSysDirectory(), DIR_SEP, APPROVED_LIST_FILENAME), + &temp, &error)) + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to load approved game settings list {}", + APPROVED_LIST_FILENAME); + WARN_LOG_FMT(ACHIEVEMENTS, "Error: {}", error); + return; + } + auto context = Common::SHA1::CreateContext(); + context->Update(temp.serialize()); + auto digest = context->Finish(); + if (digest != APPROVED_LIST_HASH) + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to verify approved game settings list {}", + APPROVED_LIST_FILENAME); + WARN_LOG_FMT(ACHIEVEMENTS, "Expected hash {}, found hash {}", + Common::SHA1::DigestToString(APPROVED_LIST_HASH), + Common::SHA1::DigestToString(digest)); + return; + } + std::lock_guard lg{m_lock}; + m_ini_root = std::move(temp); +} + void AchievementManager::SetUpdateCallback(UpdateCallback callback) { m_update_callback = std::move(callback); @@ -322,6 +352,48 @@ bool AchievementManager::IsHardcoreModeActive() const return rc_client_is_processing_required(m_client); } +void AchievementManager::FilterApprovedPatches(std::vector& patches, + const std::string& game_ini_id) const +{ + if (!IsHardcoreModeActive()) + return; + + if (!m_ini_root.contains(game_ini_id)) + patches.clear(); + auto patch_itr = patches.begin(); + while (patch_itr != patches.end()) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Verifying patch {}", patch_itr->name); + + auto context = Common::SHA1::CreateContext(); + context->Update(Common::BitCastToArray(static_cast(patch_itr->entries.size()))); + for (const auto& entry : patch_itr->entries) + { + context->Update(Common::BitCastToArray(entry.type)); + context->Update(Common::BitCastToArray(entry.address)); + context->Update(Common::BitCastToArray(entry.value)); + context->Update(Common::BitCastToArray(entry.comparand)); + context->Update(Common::BitCastToArray(entry.conditional)); + } + auto digest = context->Finish(); + + bool verified = m_ini_root.get(game_ini_id).contains(Common::SHA1::DigestToString(digest)); + if (!verified) + { + patch_itr = patches.erase(patch_itr); + OSD::AddMessage( + fmt::format("Failed to verify patch {} from file {}.", patch_itr->name, game_ini_id), + OSD::Duration::VERY_LONG, OSD::Color::RED); + OSD::AddMessage("Disable hardcore mode to enable this patch.", OSD::Duration::VERY_LONG, + OSD::Color::RED); + } + else + { + patch_itr++; + } + } +} + void AchievementManager::SetSpectatorMode() { rc_client_set_spectator_mode_enabled(m_client, Config::Get(Config::RA_SPECTATOR_ENABLED)); diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 3996003040..efddd003a9 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -27,6 +27,7 @@ #include "Common/CommonTypes.h" #include "Common/Event.h" #include "Common/HttpRequest.h" +#include "Common/JsonUtil.h" #include "Common/WorkQueueThread.h" #include "DiscIO/Volume.h" #include "VideoCommon/Assets/CustomTextureData.h" @@ -37,6 +38,11 @@ class CPUThreadGuard; class System; } // namespace Core +namespace PatchEngine +{ +struct Patch; +} // namespace PatchEngine + class AchievementManager { public: @@ -60,6 +66,10 @@ public: 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 = { + 0x01, 0x1E, 0x2E, 0x74, 0xDD, 0x07, 0x79, 0xDA, 0x0E, 0x5D, + 0xF8, 0x51, 0x09, 0xC7, 0x9B, 0x46, 0x22, 0x95, 0x50, 0xE9}; struct LeaderboardEntry { @@ -109,6 +119,9 @@ public: std::recursive_mutex& GetLock(); void SetHardcoreMode(); 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 SetSpectatorMode(); std::string_view GetPlayerDisplayName() const; u32 GetPlayerScore() const; @@ -132,7 +145,7 @@ public: void Shutdown(); private: - AchievementManager() = default; + AchievementManager() { LoadApprovedList(); }; struct FilereaderState { @@ -140,6 +153,8 @@ private: std::unique_ptr volume; }; + void 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); @@ -211,6 +226,9 @@ private: 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(); + picojson::value m_ini_root; + std::string m_game_ini_id; + std::unordered_map m_leaderboard_map; bool m_challenges_updated = false; std::unordered_set m_active_challenges; diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp index f020c754eb..0728637e47 100644 --- a/Source/Core/Core/PatchEngine.cpp +++ b/Source/Core/Core/PatchEngine.cpp @@ -182,6 +182,13 @@ void LoadPatches() LoadPatchSection("OnFrame", &s_on_frame, globalIni, localIni); +#ifdef USE_RETRO_ACHIEVEMENTS + { + std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; + AchievementManager::GetInstance().FilterApprovedPatches(s_on_frame, sconfig.GetGameID()); + } +#endif // USE_RETRO_ACHIEVEMENTS + // Check if I'm syncing Codes if (Config::Get(Config::SESSION_CODE_SYNC_OVERRIDE)) { @@ -197,9 +204,6 @@ void LoadPatches() static void ApplyPatches(const Core::CPUThreadGuard& guard, const std::vector& patches) { - if (AchievementManager::GetInstance().IsHardcoreModeActive()) - return; - for (const Patch& patch : patches) { if (patch.enabled) -- cgit v1.2.3 From 3ca50f7879d79339fac66e79bdd2ddfa5e69235f Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sun, 7 Jul 2024 21:03:29 +0200 Subject: Implement File::GetExePath() for FreeBSD --- Source/Core/Common/FileUtil.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'Source/Core') diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 5582018b30..545bfaba44 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -60,6 +60,10 @@ #include "jni/AndroidCommon/AndroidCommon.h" #endif +#if defined(__FreeBSD__) +#include +#endif + namespace fs = std::filesystem; namespace File @@ -738,6 +742,15 @@ std::string GetExePath() return PathToString(exe_path_absolute); #elif defined(__APPLE__) return GetBundleDirectory(); +#elif defined(__FreeBSD__) + int name[4]{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; + size_t length = 0; + if (sysctl(name, 4, nullptr, &length, nullptr, 0) != 0 || length == 0) + return {}; + std::string dolphin_exe_path(length, '\0'); + if (sysctl(name, 4, dolphin_exe_path.data(), &length, nullptr, 0) != 0) + return {}; + return dolphin_exe_path; #else char dolphin_exe_path[PATH_MAX]; ssize_t len = ::readlink("/proc/self/exe", dolphin_exe_path, sizeof(dolphin_exe_path)); -- cgit v1.2.3