diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 08df434fa5..4a4b1d2baf 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -79,29 +79,29 @@ void AchievementManager::Init() } } -picojson::value AchievementManager::LoadApprovedList() +auto AchievementManager::LoadApprovedList() -> std::variant { 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 {}; + error = fmt::format("Failed to load approved game settings list {}. Error: {}", + APPROVED_LIST_FILENAME, error); + WARN_LOG_FMT(ACHIEVEMENTS, "{}", error); + return error; } 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 {}; + error = fmt::format( + "Failed to verify approved game settings list {}. Expected hash {}, found hash {}", + APPROVED_LIST_FILENAME, Common::SHA1::DigestToString(APPROVED_LIST_HASH), + Common::SHA1::DigestToString(digest)); + WARN_LOG_FMT(ACHIEVEMENTS, "{}", error); + return error; } return temp; } @@ -386,6 +386,15 @@ bool AchievementManager::IsHardcoreModeActive() const return rc_client_is_processing_required(m_client); } +bool AchievementManager::IsApprovedCodesListValid(std::string* error_out) const +{ + std::lock_guard lg{m_lock}; + const bool is_valid = std::holds_alternative(*m_ini_root); + if (error_out && !is_valid) + *error_out = std::get(*m_ini_root); + return is_valid; +} + template void AchievementManager::FilterApprovedIni(std::vector& codes, const std::string& game_id, u16 revision) const @@ -402,7 +411,7 @@ void AchievementManager::FilterApprovedIni(std::vector& codes, const std::str return; // Approved codes list failed to hash - if (!m_ini_root->is()) + if (!std::holds_alternative(*m_ini_root)) { codes.clear(); return; @@ -423,11 +432,13 @@ bool AchievementManager::CheckApprovedCode(const T& code, const std::string& gam return true; // Approved codes list failed to hash - if (!m_ini_root->is()) + if (!std::holds_alternative(*m_ini_root)) return false; INFO_LOG_FMT(ACHIEVEMENTS, "Verifying code {}", code.name); + const picojson::value& ini_root = std::get(*m_ini_root); + bool verified = false; auto hash = Common::SHA1::DigestToString(GetCodeHash(code)); @@ -435,7 +446,7 @@ bool AchievementManager::CheckApprovedCode(const T& code, const std::string& gam for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(game_id, revision)) { auto config = filename.substr(0, filename.length() - 4); - if (m_ini_root->contains(config) && m_ini_root->get(config).contains(hash)) + if (ini_root.contains(config) && ini_root.get(config).contains(hash)) verified = true; } diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index f46b7e9da3..b35fe7c184 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -134,6 +135,7 @@ public: std::recursive_mutex& GetLock(); bool IsHardcoreModeActive() const; + bool IsApprovedCodesListValid(std::string* error_out = nullptr) const; void FilterApprovedPatches(std::vector& patches, const std::string& game_id, u16 revision) const; void FilterApprovedGeckoCodes(std::vector& codes, const std::string& game_id, @@ -176,7 +178,8 @@ private: std::unique_ptr volume; }; - static picojson::value LoadApprovedList(); + using ErrorString = std::string; + static std::variant LoadApprovedList(); static void* FilereaderOpenByFilepath(const char* path_utf8); static void* FilereaderOpenByVolume(const char* path_utf8); @@ -259,7 +262,7 @@ 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(); - Common::Lazy m_ini_root{LoadApprovedList}; + Common::Lazy> m_ini_root{LoadApprovedList}; std::unordered_map m_leaderboard_map; bool m_challenges_updated = false; @@ -302,6 +305,8 @@ public: constexpr bool IsHardcoreModeActive() { return false; } + constexpr bool IsApprovedCodesListValid(std::string* error_out = nullptr) { return true; } + constexpr bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_id) { return true; diff --git a/Source/UnitTests/Core/PatchAllowlistTest.cpp b/Source/UnitTests/Core/PatchAllowlistTest.cpp index 2aae282216..084f2633d0 100644 --- a/Source/UnitTests/Core/PatchAllowlistTest.cpp +++ b/Source/UnitTests/Core/PatchAllowlistTest.cpp @@ -18,6 +18,7 @@ #include "Common/IOFile.h" #include "Common/IniFile.h" #include "Common/JsonUtil.h" +#include "Core/AchievementManager.h" #include "Core/ActionReplay.h" #include "Core/CheatCodes.h" #include "Core/GeckoCode.h" @@ -38,7 +39,14 @@ void ReadVerified(const Common::IniFile& ini, const std::string& filename, void CheckHash(const std::string& game_id, GameHashes* game_hashes, const std::string& hash, const std::string& patch_name); -TEST(PatchAllowlist, VerifyHashes) +TEST(PatchAllowlist, VerifyJsonMatchesExecutable) +{ + std::string error; + if (!AchievementManager::GetInstance().IsApprovedCodesListValid(&error)) + ADD_FAILURE() << error; +} + +TEST(PatchAllowlist, VerifyInisMatchJson) { // Load allowlist static constexpr std::string_view APPROVED_LIST_FILENAME = "ApprovedInis.json"; diff --git a/Source/UnitTests/UnitTests.vcxproj b/Source/UnitTests/UnitTests.vcxproj index 45702ed7fd..255db7c9dc 100644 --- a/Source/UnitTests/UnitTests.vcxproj +++ b/Source/UnitTests/UnitTests.vcxproj @@ -103,6 +103,7 @@ +