diff --git a/Source/UnitTests/Core/PatchAllowlistTest.cpp b/Source/UnitTests/Core/PatchAllowlistTest.cpp
index 779b49791f..2c5bbf6361 100644
--- a/Source/UnitTests/Core/PatchAllowlistTest.cpp
+++ b/Source/UnitTests/Core/PatchAllowlistTest.cpp
@@ -18,7 +18,10 @@
#include "Common/IOFile.h"
#include "Common/IniFile.h"
#include "Common/JsonUtil.h"
+#include "Core/ActionReplay.h"
#include "Core/CheatCodes.h"
+#include "Core/GeckoCode.h"
+#include "Core/GeckoCodeConfig.h"
#include "Core/PatchEngine.h"
struct GameHashes
@@ -27,6 +30,11 @@ struct GameHashes
std::map hashes;
};
+using AllowList = std::map;
+
+void CheckHash(const std::string& game_id, GameHashes* game_hashes, const std::string& hash,
+ const std::string& patch_name);
+
TEST(PatchAllowlist, VerifyHashes)
{
// Load allowlist
@@ -42,9 +50,9 @@ TEST(PatchAllowlist, VerifyHashes)
const auto& list_filepath = fmt::format("{}{}{}", sys_directory, DIR_SEP, APPROVED_LIST_FILENAME);
ASSERT_TRUE(JsonFromFile(list_filepath, &json_tree, &error))
<< "Failed to open file at " << list_filepath;
- // Parse allowlist - Map
+ // Parse allowlist - Map>
ASSERT_TRUE(json_tree.is());
- std::map allow_list;
+ AllowList allow_list;
for (const auto& entry : json_tree.get())
{
ASSERT_TRUE(entry.second.is());
@@ -69,12 +77,23 @@ TEST(PatchAllowlist, VerifyHashes)
std::string game_id = file.virtualName.substr(0, file.virtualName.find_first_of('.'));
std::vector patches;
PatchEngine::LoadPatchSection("OnFrame", &patches, ini_file, Common::IniFile());
+ std::vector geckos = Gecko::LoadCodes(Common::IniFile(), ini_file);
+ std::vector action_replays =
+ ActionReplay::LoadCodes(Common::IniFile(), ini_file);
// Filter patches for RetroAchievements approved
ReadEnabledOrDisabled(ini_file, "OnFrame", false, &patches);
ReadEnabledOrDisabled(ini_file, "Patches_RetroAchievements_Verified", true,
&patches);
+ ReadEnabledOrDisabled(ini_file, "Gecko", false, &geckos);
+ ReadEnabledOrDisabled(ini_file, "Gecko_Enabled", false, &geckos);
+ ReadEnabledOrDisabled(ini_file, "Gecko_RetroAchievements_Verified", true,
+ &geckos);
+ ReadEnabledOrDisabled(ini_file, "ActionReplay", false, &action_replays);
+ ReadEnabledOrDisabled(ini_file, "AR_RetroAchievements_Verified", true,
+ &action_replays);
// Get game section from allow list
auto game_itr = allow_list.find(game_id);
+ bool itr_end = (game_itr == allow_list.end());
// Iterate over approved patches
for (const auto& patch : patches)
{
@@ -92,38 +111,51 @@ TEST(PatchAllowlist, VerifyHashes)
context->Update(Common::BitCastToArray(entry.conditional));
}
auto digest = context->Finish();
- std::string hash = Common::SHA1::DigestToString(digest);
- // Check patch in list
- if (game_itr == allow_list.end())
- {
- // Report: no patches in game found in list
- ADD_FAILURE() << "Approved hash missing from list." << std::endl
- << "Game ID: " << game_id << std::endl
- << "Patch: \"" << hash << "\" : \"" << patch.name << "\"";
+ CheckHash(game_id, itr_end ? nullptr : &game_itr->second,
+ Common::SHA1::DigestToString(digest), patch.name);
+ }
+ // Iterate over approved geckos
+ for (const auto& code : geckos)
+ {
+ if (!code.enabled)
continue;
- }
- auto hash_itr = game_itr->second.hashes.find(hash);
- if (hash_itr == game_itr->second.hashes.end())
+ // Hash patch
+ auto context = Common::SHA1::CreateContext();
+ context->Update(Common::BitCastToArray(static_cast(code.codes.size())));
+ for (const auto& entry : code.codes)
{
- // Report: patch not found in list
- ADD_FAILURE() << "Approved hash missing from list." << std::endl
- << "Game ID: " << game_id << ":" << game_itr->second.game_title << std::endl
- << "Patch: \"" << hash << "\" : \"" << patch.name << "\"";
+ context->Update(Common::BitCastToArray(entry.address));
+ context->Update(Common::BitCastToArray(entry.data));
}
- else
+ auto digest = context->Finish();
+ CheckHash(game_id, itr_end ? nullptr : &game_itr->second,
+ Common::SHA1::DigestToString(digest), code.name);
+ }
+ // Iterate over approved AR codes
+ for (const auto& code : action_replays)
+ {
+ if (!code.enabled)
+ continue;
+ // Hash patch
+ auto context = Common::SHA1::CreateContext();
+ context->Update(Common::BitCastToArray(static_cast(code.ops.size())));
+ for (const auto& entry : code.ops)
{
- // Remove patch from map if found
- game_itr->second.hashes.erase(hash_itr);
+ context->Update(Common::BitCastToArray(entry.cmd_addr));
+ context->Update(Common::BitCastToArray(entry.value));
}
+ auto digest = context->Finish();
+ CheckHash(game_id, itr_end ? nullptr : &game_itr->second,
+ Common::SHA1::DigestToString(digest), code.name);
}
// Report missing patches in map
- if (game_itr == allow_list.end())
+ if (itr_end)
continue;
for (auto& remaining_hashes : game_itr->second.hashes)
{
ADD_FAILURE() << "Hash in list not approved in ini." << std::endl
<< "Game ID: " << game_id << ":" << game_itr->second.game_title << std::endl
- << "Patch: " << remaining_hashes.second << ":" << remaining_hashes.first;
+ << "Code: " << remaining_hashes.second << ":" << remaining_hashes.first;
}
// Remove section from map
allow_list.erase(game_itr);
@@ -136,3 +168,30 @@ TEST(PatchAllowlist, VerifyHashes)
<< remaining_games.second.game_title;
}
}
+
+void CheckHash(const std::string& game_id, GameHashes* game_hashes, const std::string& hash,
+ const std::string& patch_name)
+{
+ // Check patch in list
+ if (game_hashes == nullptr)
+ {
+ // Report: no patches in game found in list
+ ADD_FAILURE() << "Approved hash missing from list." << std::endl
+ << "Game ID: " << game_id << std::endl
+ << "Code: \"" << hash << "\": \"" << patch_name << "\"";
+ return;
+ }
+ auto hash_itr = game_hashes->hashes.find(hash);
+ if (hash_itr == game_hashes->hashes.end())
+ {
+ // Report: patch not found in list
+ ADD_FAILURE() << "Approved hash missing from list." << std::endl
+ << "Game ID: " << game_id << ":" << game_hashes->game_title << std::endl
+ << "Code: \"" << hash << "\": \"" << patch_name << "\"";
+ }
+ else
+ {
+ // Remove patch from map if found
+ game_hashes->hashes.erase(hash_itr);
+ }
+}