diff --git a/Source/Core/Common/Crypto/SHA1.cpp b/Source/Core/Common/Crypto/SHA1.cpp index 85ad54f75d..44d5f9c3dd 100644 --- a/Source/Core/Common/Crypto/SHA1.cpp +++ b/Source/Core/Common/Crypto/SHA1.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include "Common/Assert.h" @@ -390,17 +391,11 @@ Digest CalculateDigest(const u8* msg, size_t len) std::string DigestToString(const Digest& digest) { - static constexpr std::array lookup = {'0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - std::string hash; - hash.reserve(digest.size() * 2); - for (size_t i = 0; i < digest.size(); ++i) - { - const u8 upper = static_cast((digest[i] >> 4) & 0xf); - const u8 lower = static_cast(digest[i] & 0xf); - hash.push_back(lookup[upper]); - hash.push_back(lookup[lower]); - } - return hash; + return fmt::format("{:02X}", fmt::join(digest, "")); +} + +std::string DigestToSource(const Digest& digest) +{ + return fmt::format("{{0x{:02X}}}", fmt::join(digest, ", 0x")); } } // namespace Common::SHA1 diff --git a/Source/Core/Common/Crypto/SHA1.h b/Source/Core/Common/Crypto/SHA1.h index 6fd29172c0..feb92f31fd 100644 --- a/Source/Core/Common/Crypto/SHA1.h +++ b/Source/Core/Common/Crypto/SHA1.h @@ -58,4 +58,5 @@ inline Digest CalculateDigest(const std::array& msg) } std::string DigestToString(const Digest& digest); +std::string DigestToSource(const Digest& digest); } // namespace Common::SHA1 diff --git a/Source/Core/Core/AchievementApprovedHash.h b/Source/Core/Core/AchievementApprovedHash.h new file mode 100644 index 0000000000..2116fb2c49 --- /dev/null +++ b/Source/Core/Core/AchievementApprovedHash.h @@ -0,0 +1,13 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/Crypto/SHA1.h" + +static constexpr std::string_view ACHIEVEMENT_APPROVED_LIST_FILENAME = "ApprovedInis.json"; +// After building tests, find the new hash with: +// ./Binaries/Tests/tests --gtest_filter=PatchAllowlist.VerifyHashes +static const inline Common::SHA1::Digest ACHIEVEMENT_APPROVED_LIST_HASH = { + 0xEA, 0x2F, 0x74, 0xA1, 0x6C, 0xF3, 0xB5, 0xD4, 0x8A, 0xAF, + 0x03, 0x30, 0x58, 0x2A, 0xE0, 0xF7, 0x0A, 0x88, 0x86, 0xB3}; diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index d8f6c3bdc0..7914431b6d 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -21,6 +21,7 @@ #include "Common/StringUtil.h" #include "Common/Version.h" #include "Common/WorkQueueThread.h" +#include "Core/AchievementApprovedHash.h" #include "Core/ActionReplay.h" #include "Core/Config/AchievementSettings.h" #include "Core/Config/FreeLookSettings.h" @@ -100,23 +101,24 @@ picojson::value AchievementManager::LoadApprovedList() { picojson::value temp; std::string error; - if (!JsonFromFile(fmt::format("{}{}{}", File::GetSysDirectory(), DIR_SEP, APPROVED_LIST_FILENAME), + if (!JsonFromFile(fmt::format("{}{}{}", File::GetSysDirectory(), DIR_SEP, + ACHIEVEMENT_APPROVED_LIST_FILENAME), &temp, &error)) { WARN_LOG_FMT(ACHIEVEMENTS, "Failed to load approved game settings list {}", - APPROVED_LIST_FILENAME); + ACHIEVEMENT_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) + if (digest != ACHIEVEMENT_APPROVED_LIST_HASH) { WARN_LOG_FMT(ACHIEVEMENTS, "Failed to verify approved game settings list {}", - APPROVED_LIST_FILENAME); + ACHIEVEMENT_APPROVED_LIST_FILENAME); WARN_LOG_FMT(ACHIEVEMENTS, "Expected hash {}, found hash {}", - Common::SHA1::DigestToString(APPROVED_LIST_HASH), + Common::SHA1::DigestToString(ACHIEVEMENT_APPROVED_LIST_HASH), Common::SHA1::DigestToString(digest)); return {}; } diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 2ab23a4368..8afaa9450e 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -81,10 +81,6 @@ 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 = { - 0xEA, 0x2F, 0x74, 0xA1, 0x6C, 0xF3, 0xB5, 0xD4, 0x8A, 0xAF, - 0x03, 0x30, 0x58, 0x2A, 0xE0, 0xF7, 0x0A, 0x88, 0x86, 0xB3}; struct LeaderboardEntry { diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index e2ae3e9919..bd4aa1fb19 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(core + AchievementApprovedHash.h AchievementManager.cpp AchievementManager.h ActionReplay.cpp diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 34e4b83f13..4ac02bd5c5 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -183,6 +183,7 @@ + diff --git a/Source/UnitTests/Core/PatchAllowlistTest.cpp b/Source/UnitTests/Core/PatchAllowlistTest.cpp index 4fa6f0f328..022e533112 100644 --- a/Source/UnitTests/Core/PatchAllowlistTest.cpp +++ b/Source/UnitTests/Core/PatchAllowlistTest.cpp @@ -18,7 +18,7 @@ #include "Common/FileUtil.h" #include "Common/IniFile.h" #include "Common/JsonUtil.h" -#include "Core/AchievementManager.h" +#include "Core/AchievementApprovedHash.h" #include "Core/ActionReplay.h" #include "Core/GeckoCode.h" #include "Core/GeckoCodeConfig.h" @@ -128,23 +128,24 @@ TEST(PatchAllowlist, VerifyHashes) auto context = Common::SHA1::CreateContext(); context->Update(new_allowlist_str); auto digest = context->Finish(); - if (digest != AchievementManager::APPROVED_LIST_HASH) + if (digest != ACHIEVEMENT_APPROVED_LIST_HASH) { - ADD_FAILURE() << "Approved list hash does not match the one in AchievementMananger." + ADD_FAILURE() << "Approved list hash does not match the one in AchievementApprovedHash.h." << std::endl << "Please update APPROVED_LIST_HASH to the following:" << std::endl - << Common::SHA1::DigestToString(digest); + << Common::SHA1::DigestToSource(digest); } // Compare with old allowlist - static constexpr std::string_view APPROVED_LIST_FILENAME = "ApprovedInis.json"; std::string old_allowlist; std::string error; - const auto& list_filepath = fmt::format("{}{}{}", sys_directory, DIR_SEP, APPROVED_LIST_FILENAME); + const auto& list_filepath = + fmt::format("{}{}{}", sys_directory, DIR_SEP, ACHIEVEMENT_APPROVED_LIST_FILENAME); if (!File::ReadFileToString(list_filepath, old_allowlist) || old_allowlist != new_allowlist_str) { - static constexpr std::string_view NEW_APPROVED_LIST_FILENAME = "New-ApprovedInis.json"; + static constexpr std::string_view NEW_ACHIEVEMENT_APPROVED_LIST_FILENAME = + "New-ApprovedInis.json"; const auto& new_list_filepath = - fmt::format("{}{}{}", sys_directory, DIR_SEP, NEW_APPROVED_LIST_FILENAME); + fmt::format("{}{}{}", sys_directory, DIR_SEP, NEW_ACHIEVEMENT_APPROVED_LIST_FILENAME); if (!JsonToFile(new_list_filepath, picojson::value(new_allowlist), false)) { ADD_FAILURE() << "Failed to write new approved list to " << list_filepath;