diff --git a/Data/Sys/ApprovedInis.json b/Data/Sys/ApprovedInis.json index 2132bd62fe..c8e942b364 100644 --- a/Data/Sys/ApprovedInis.json +++ b/Data/Sys/ApprovedInis.json @@ -160,6 +160,10 @@ "title": "Gladius", "3D0894616C9A7FA5ED91C1D2F461BF14DF47ECEC": "Fix freeze in opening cutscene" }, + "GMSE01": { + "title": "Super Mario Sunshine", + "BD718F961DBA5372B1D0257D454D535746C453A0": "Widescreen" + }, "GNHE5d": { "title": "NHL HITZ 2002", "89393A24E2336841AA4CD0AD3BE1C9A66B89E9EF": "Nop Hack" diff --git a/Data/Sys/GameSettings/GMSE01.ini b/Data/Sys/GameSettings/GMSE01.ini index 32eb737097..3b2dc268e2 100644 --- a/Data/Sys/GameSettings/GMSE01.ini +++ b/Data/Sys/GameSettings/GMSE01.ini @@ -175,3 +175,6 @@ C2363138 00000009 7C631670 54A5F0BE 7C630194 7C630214 60000000 00000000 + +[Gecko_RetroAchievements_Verified] +$Widescreen diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index c724476ad7..a40f4aaeb5 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -16,6 +16,7 @@ #include "Common/Assert.h" #include "Common/BitUtils.h" #include "Common/CommonPaths.h" +#include "Common/Config/Config.h" #include "Common/FileUtil.h" #include "Common/IOFile.h" #include "Common/Image.h" @@ -23,10 +24,12 @@ #include "Common/ScopeGuard.h" #include "Common/Version.h" #include "Common/WorkQueueThread.h" +#include "Core/ActionReplay.h" #include "Core/Config/AchievementSettings.h" #include "Core/Config/FreeLookSettings.h" #include "Core/Config/MainSettings.h" #include "Core/Core.h" +#include "Core/GeckoCode.h" #include "Core/HW/Memmap.h" #include "Core/HW/VideoInterface.h" #include "Core/PatchEngine.h" @@ -64,6 +67,7 @@ void AchievementManager::Init() [](const char* message, const rc_client_t* client) { INFO_LOG_FMT(ACHIEVEMENTS, "{}", message); }); + Config::AddConfigChangedCallback([this] { SetHardcoreMode(); }); SetHardcoreMode(); m_queue.Reset("AchievementManagerQueue", [](const std::function& func) { func(); }); m_image_queue.Reset("AchievementManagerImageQueue", @@ -368,7 +372,6 @@ void AchievementManager::SetHardcoreMode() if (Config::Get(Config::MAIN_EMULATION_SPEED) < 1.0f) Config::SetBaseOrCurrent(Config::MAIN_EMULATION_SPEED, 1.0f); Config::SetBaseOrCurrent(Config::FREE_LOOK_ENABLED, false); - Config::SetBaseOrCurrent(Config::MAIN_ENABLE_CHEATS, false); } } @@ -382,10 +385,11 @@ 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 +template +void AchievementManager::FilterApprovedIni(std::vector& codes, + const std::string& game_ini_id) const { - if (patches.empty()) + if (codes.empty()) { // There's nothing to verify, so let's save ourselves some work return; @@ -396,46 +400,120 @@ void AchievementManager::FilterApprovedPatches(std::vector& if (!IsHardcoreModeActive()) return; + // Approved codes list failed to hash + if (!m_ini_root->is()) + { + codes.clear(); + return; + } + + for (auto& code : codes) + { + if (code.enabled && !CheckApprovedCode(code, game_ini_id)) + code.enabled = false; + } +} + +template +bool AchievementManager::CheckApprovedCode(const T& code, const std::string& game_ini_id) const +{ + if (!IsHardcoreModeActive()) + return true; + + // Approved codes list failed to hash + if (!m_ini_root->is()) + return false; + const bool known_id = m_ini_root->contains(game_ini_id); - auto patch_itr = patches.begin(); - while (patch_itr != patches.end()) + INFO_LOG_FMT(ACHIEVEMENTS, "Verifying code {}", code.name); + + bool verified = false; + + if (known_id) { - INFO_LOG_FMT(ACHIEVEMENTS, "Verifying patch {}", patch_itr->name); + auto digest = GetCodeHash(code); - bool verified = false; - - if (known_id) - { - 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(); - - 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++; - } + verified = m_ini_root->get(game_ini_id).contains(Common::SHA1::DigestToString(digest)); } + + if (!verified) + { + OSD::AddMessage(fmt::format("Failed to verify code {} from file {}.", code.name, game_ini_id), + OSD::Duration::VERY_LONG, OSD::Color::RED); + OSD::AddMessage("Disable hardcore mode to enable this code.", OSD::Duration::VERY_LONG, + OSD::Color::RED); + } + return verified; +} + +Common::SHA1::Digest AchievementManager::GetCodeHash(const PatchEngine::Patch& patch) const +{ + auto context = Common::SHA1::CreateContext(); + context->Update(Common::BitCastToArray(static_cast(patch.entries.size()))); + for (const auto& entry : patch.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)); + } + return context->Finish(); +} + +Common::SHA1::Digest AchievementManager::GetCodeHash(const Gecko::GeckoCode& code) const +{ + auto context = Common::SHA1::CreateContext(); + context->Update(Common::BitCastToArray(static_cast(code.codes.size()))); + for (const auto& entry : code.codes) + { + context->Update(Common::BitCastToArray(entry.address)); + context->Update(Common::BitCastToArray(entry.data)); + } + return context->Finish(); +} + +Common::SHA1::Digest AchievementManager::GetCodeHash(const ActionReplay::ARCode& code) const +{ + auto context = Common::SHA1::CreateContext(); + context->Update(Common::BitCastToArray(static_cast(code.ops.size()))); + for (const auto& entry : code.ops) + { + context->Update(Common::BitCastToArray(entry.cmd_addr)); + context->Update(Common::BitCastToArray(entry.value)); + } + return context->Finish(); +} + +void AchievementManager::FilterApprovedPatches(std::vector& patches, + const std::string& game_ini_id) const +{ + FilterApprovedIni(patches, game_ini_id); +} + +void AchievementManager::FilterApprovedGeckoCodes(std::vector& codes, + const std::string& game_ini_id) const +{ + FilterApprovedIni(codes, game_ini_id); +} + +void AchievementManager::FilterApprovedARCodes(std::vector& codes, + const std::string& game_ini_id) const +{ + FilterApprovedIni(codes, game_ini_id); +} + +bool AchievementManager::CheckApprovedGeckoCode(const Gecko::GeckoCode& code, + const std::string& game_ini_id) const +{ + return CheckApprovedCode(code, game_ini_id); +} + +bool AchievementManager::CheckApprovedARCode(const ActionReplay::ARCode& code, + const std::string& game_ini_id) const +{ + return CheckApprovedCode(code, game_ini_id); } void AchievementManager::SetSpectatorMode() diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 174b4abce1..9de18f1711 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -45,6 +45,16 @@ namespace PatchEngine struct Patch; } // namespace PatchEngine +namespace Gecko +{ +class GeckoCode; +} // namespace Gecko + +namespace ActionReplay +{ +struct ARCode; +} // namespace ActionReplay + class AchievementManager { public: @@ -70,8 +80,8 @@ public: 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 = { - 0xCC, 0xB4, 0x05, 0x2D, 0x2B, 0xEE, 0xF4, 0x06, 0x4A, 0xC9, - 0x57, 0x5D, 0xA9, 0xE9, 0xDE, 0xB7, 0x98, 0xF8, 0x1A, 0x6D}; + 0xA4, 0x98, 0x59, 0x23, 0x10, 0x56, 0x45, 0x30, 0xA9, 0xC5, + 0x68, 0x5A, 0xB6, 0x47, 0x67, 0xF8, 0xF0, 0x7D, 0x1D, 0x14}; struct LeaderboardEntry { @@ -125,8 +135,16 @@ public: 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 FilterApprovedGeckoCodes(std::vector& codes, + const std::string& game_ini_id) const; + void FilterApprovedARCodes(std::vector& codes, + const std::string& game_ini_id) const; + bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_ini_id) const; + bool CheckApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_ini_id) const; + void SetSpectatorMode(); std::string_view GetPlayerDisplayName() const; u32 GetPlayerScore() const; @@ -181,6 +199,14 @@ private: void* userdata); void DisplayWelcomeMessage(); + template + void FilterApprovedIni(std::vector& codes, const std::string& game_ini_id) const; + template + bool CheckApprovedCode(const T& code, const std::string& game_ini_id) 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); @@ -265,6 +291,18 @@ public: constexpr bool IsHardcoreModeActive() { return false; } + constexpr bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code, + const std::string& game_ini_id) + { + return true; + }; + + constexpr bool CheckApprovedARCode(const ActionReplay::ARCode& code, + const std::string& game_ini_id) + { + return true; + }; + constexpr void LoadGame(const std::string&, const DiscIO::Volume*) {} constexpr void SetBackgroundExecutionAllowed(bool allowed) {} diff --git a/Source/Core/Core/ActionReplay.cpp b/Source/Core/Core/ActionReplay.cpp index 3052f06dec..d3171cdd25 100644 --- a/Source/Core/Core/ActionReplay.cpp +++ b/Source/Core/Core/ActionReplay.cpp @@ -39,6 +39,7 @@ #include "Common/MsgHandler.h" #include "Core/ARDecrypt.h" +#include "Core/AchievementManager.h" #include "Core/CheatCodes.h" #include "Core/Config/MainSettings.h" #include "Core/PowerPC/MMU.h" @@ -112,7 +113,7 @@ struct ARAddr // ---------------------- // AR Remote Functions -void ApplyCodes(std::span codes) +void ApplyCodes(std::span codes, const std::string& game_id) { if (!Config::AreCheatsEnabled()) return; @@ -121,7 +122,10 @@ void ApplyCodes(std::span codes) s_disable_logging = false; s_active_codes.clear(); std::copy_if(codes.begin(), codes.end(), std::back_inserter(s_active_codes), - [](const ARCode& code) { return code.enabled; }); + [&game_id](const ARCode& code) { + return code.enabled && + AchievementManager::GetInstance().CheckApprovedARCode(code, game_id); + }); s_active_codes.shrink_to_fit(); } @@ -169,9 +173,10 @@ void AddCode(ARCode code) } } -void LoadAndApplyCodes(const Common::IniFile& global_ini, const Common::IniFile& local_ini) +void LoadAndApplyCodes(const Common::IniFile& global_ini, const Common::IniFile& local_ini, + const std::string& game_id) { - ApplyCodes(LoadCodes(global_ini, local_ini)); + ApplyCodes(LoadCodes(global_ini, local_ini), game_id); } // Parses the Action Replay section of a game ini file. diff --git a/Source/Core/Core/ActionReplay.h b/Source/Core/Core/ActionReplay.h index ee2cb8b485..1cfbb0a1fe 100644 --- a/Source/Core/Core/ActionReplay.h +++ b/Source/Core/Core/ActionReplay.h @@ -45,12 +45,13 @@ struct ARCode void RunAllActive(const Core::CPUThreadGuard& cpu_guard); -void ApplyCodes(std::span codes); +void ApplyCodes(std::span codes, const std::string& game_id); void SetSyncedCodesAsActive(); void UpdateSyncedCodes(std::span codes); std::vector ApplyAndReturnCodes(std::span codes); void AddCode(ARCode new_code); -void LoadAndApplyCodes(const Common::IniFile& global_ini, const Common::IniFile& local_ini); +void LoadAndApplyCodes(const Common::IniFile& global_ini, const Common::IniFile& local_ini, + const std::string& game_id); std::vector LoadCodes(const Common::IniFile& global_ini, const Common::IniFile& local_ini); void SaveCodes(Common::IniFile* local_ini, std::span codes); diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index c9cd0333a9..0442347fdd 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -751,8 +751,7 @@ bool IsDefaultGCIFolderPathConfigured(ExpansionInterface::Slot slot) bool AreCheatsEnabled() { - return Config::Get(::Config::MAIN_ENABLE_CHEATS) && - !AchievementManager::GetInstance().IsHardcoreModeActive(); + return Config::Get(::Config::MAIN_ENABLE_CHEATS); } bool IsDebuggingEnabled() diff --git a/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp b/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp index 75b1bda454..5f772fd0e3 100644 --- a/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp +++ b/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp @@ -11,6 +11,7 @@ #include "Common/Config/Config.h" #include "Common/FileUtil.h" +#include "Core/Config/AchievementSettings.h" #include "Core/Config/GraphicsSettings.h" #include "Core/Config/MainSettings.h" #include "Core/Config/SYSCONFSettings.h" @@ -33,6 +34,9 @@ public: layer->Set(Config::MAIN_CPU_THREAD, m_settings.cpu_thread); layer->Set(Config::MAIN_CPU_CORE, m_settings.cpu_core); layer->Set(Config::MAIN_ENABLE_CHEATS, m_settings.enable_cheats); +#ifdef USE_RETRO_ACHIEVEMENTS + layer->Set(Config::RA_HARDCORE_ENABLED, m_settings.enable_hardcore); +#endif // USE_RETRO_ACHIEVEMENTS layer->Set(Config::MAIN_GC_LANGUAGE, m_settings.selected_language); layer->Set(Config::MAIN_OVERRIDE_REGION_SETTINGS, m_settings.override_region_settings); layer->Set(Config::MAIN_DSP_HLE, m_settings.dsp_hle); diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index 92f389545d..0d43c5a507 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -15,6 +15,7 @@ #include "Common/Config/Config.h" #include "Common/FileUtil.h" +#include "Core/AchievementManager.h" #include "Core/Config/MainSettings.h" #include "Core/Core.h" #include "Core/Host.h" @@ -49,7 +50,7 @@ static std::vector s_active_codes; static std::vector s_synced_codes; static std::mutex s_active_codes_lock; -void SetActiveCodes(std::span gcodes) +void SetActiveCodes(std::span gcodes, const std::string& game_id) { std::lock_guard lk(s_active_codes_lock); @@ -57,8 +58,12 @@ void SetActiveCodes(std::span gcodes) if (Config::AreCheatsEnabled()) { s_active_codes.reserve(gcodes.size()); + std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_active_codes), - [](const GeckoCode& code) { return code.enabled; }); + [&game_id](const GeckoCode& code) { + return code.enabled && + AchievementManager::GetInstance().CheckApprovedGeckoCode(code, game_id); + }); } s_active_codes.shrink_to_fit(); diff --git a/Source/Core/Core/GeckoCode.h b/Source/Core/Core/GeckoCode.h index 3600d1b9ba..2931ed3641 100644 --- a/Source/Core/Core/GeckoCode.h +++ b/Source/Core/Core/GeckoCode.h @@ -60,7 +60,7 @@ constexpr u32 HLE_TRAMPOLINE_ADDRESS = INSTALLER_END_ADDRESS - 4; // preserve the emulation performance. constexpr u32 MAGIC_GAMEID = 0xD01F1BAD; -void SetActiveCodes(std::span gcodes); +void SetActiveCodes(std::span gcodes, const std::string& game_id); void SetSyncedCodesAsActive(); void UpdateSyncedCodes(std::span gcodes); std::vector SetAndReturnActiveCodes(std::span gcodes); diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 43701c75ef..82c594dff6 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -848,6 +848,7 @@ void NetPlayClient::OnStartGame(sf::Packet& packet) packet >> m_net_settings.cpu_thread; packet >> m_net_settings.cpu_core; packet >> m_net_settings.enable_cheats; + packet >> m_net_settings.enable_hardcore; packet >> m_net_settings.selected_language; packet >> m_net_settings.override_region_settings; packet >> m_net_settings.dsp_enable_jit; diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h index a20aee39ce..86f54d458d 100644 --- a/Source/Core/Core/NetPlayProto.h +++ b/Source/Core/Core/NetPlayProto.h @@ -35,6 +35,7 @@ struct NetSettings bool cpu_thread = false; PowerPC::CPUCore cpu_core{}; bool enable_cheats = false; + bool enable_hardcore = false; int selected_language = 0; bool override_region_settings = false; bool dsp_hle = false; diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 0044ce903e..ae88ba3d82 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -31,6 +31,7 @@ #include "Common/UPnP.h" #include "Common/Version.h" +#include "Core/AchievementManager.h" #include "Core/ActionReplay.h" #include "Core/Boot/Boot.h" #include "Core/Config/GraphicsSettings.h" @@ -1358,6 +1359,7 @@ bool NetPlayServer::SetupNetSettings() settings.cpu_thread = Config::Get(Config::MAIN_CPU_THREAD); settings.cpu_core = Config::Get(Config::MAIN_CPU_CORE); settings.enable_cheats = Config::AreCheatsEnabled(); + settings.enable_hardcore = AchievementManager::GetInstance().IsHardcoreModeActive(); settings.selected_language = Config::Get(Config::MAIN_GC_LANGUAGE); settings.override_region_settings = Config::Get(Config::MAIN_OVERRIDE_REGION_SETTINGS); settings.dsp_hle = Config::Get(Config::MAIN_DSP_HLE); @@ -1586,6 +1588,7 @@ bool NetPlayServer::StartGame() spac << m_settings.cpu_thread; spac << m_settings.cpu_core; spac << m_settings.enable_cheats; + spac << m_settings.enable_hardcore; spac << m_settings.selected_language; spac << m_settings.override_region_settings; spac << m_settings.dsp_enable_jit; @@ -2067,13 +2070,18 @@ bool NetPlayServer::SyncCodes() } // Sync Gecko Codes { + std::vector codes = Gecko::LoadCodes(globalIni, localIni); + +#ifdef USE_RETRO_ACHIEVEMENTS + AchievementManager::GetInstance().FilterApprovedGeckoCodes(codes, game_id); +#endif // USE_RETRO_ACHIEVEMENTS + // Create a Gecko Code Vector with just the active codes - std::vector s_active_codes = - Gecko::SetAndReturnActiveCodes(Gecko::LoadCodes(globalIni, localIni)); + std::vector active_codes = Gecko::SetAndReturnActiveCodes(codes); // Determine Codelist Size u16 codelines = 0; - for (const Gecko::GeckoCode& active_code : s_active_codes) + for (const Gecko::GeckoCode& active_code : active_codes) { INFO_LOG_FMT(NETPLAY, "Indexing {}", active_code.name); for (const Gecko::GeckoCode::Code& code : active_code.codes) @@ -2101,7 +2109,7 @@ bool NetPlayServer::SyncCodes() pac << MessageID::SyncCodes; pac << SyncCodeID::GeckoData; // Iterate through the active code vector and send each codeline - for (const Gecko::GeckoCode& active_code : s_active_codes) + for (const Gecko::GeckoCode& active_code : active_codes) { INFO_LOG_FMT(NETPLAY, "Sending {}", active_code.name); for (const Gecko::GeckoCode::Code& code : active_code.codes) @@ -2117,13 +2125,16 @@ bool NetPlayServer::SyncCodes() // Sync AR Codes { + std::vector codes = ActionReplay::LoadCodes(globalIni, localIni); +#ifdef USE_RETRO_ACHIEVEMENTS + AchievementManager::GetInstance().FilterApprovedARCodes(codes, game_id); +#endif // USE_RETRO_ACHIEVEMENTS // Create an AR Code Vector with just the active codes - std::vector s_active_codes = - ActionReplay::ApplyAndReturnCodes(ActionReplay::LoadCodes(globalIni, localIni)); + std::vector active_codes = ActionReplay::ApplyAndReturnCodes(codes); // Determine Codelist Size u16 codelines = 0; - for (const ActionReplay::ARCode& active_code : s_active_codes) + for (const ActionReplay::ARCode& active_code : active_codes) { INFO_LOG_FMT(NETPLAY, "Indexing {}", active_code.name); for (const ActionReplay::AREntry& op : active_code.ops) @@ -2151,7 +2162,7 @@ bool NetPlayServer::SyncCodes() pac << MessageID::SyncCodes; pac << SyncCodeID::ARData; // Iterate through the active code vector and send each codeline - for (const ActionReplay::ARCode& active_code : s_active_codes) + for (const ActionReplay::ARCode& active_code : active_codes) { INFO_LOG_FMT(NETPLAY, "Sending {}", active_code.name); for (const ActionReplay::AREntry& op : active_code.ops) diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp index 9880977710..e350f1e063 100644 --- a/Source/Core/Core/PatchEngine.cpp +++ b/Source/Core/Core/PatchEngine.cpp @@ -197,8 +197,8 @@ void LoadPatches() } else { - Gecko::SetActiveCodes(Gecko::LoadCodes(globalIni, localIni)); - ActionReplay::LoadAndApplyCodes(globalIni, localIni); + Gecko::SetActiveCodes(Gecko::LoadCodes(globalIni, localIni), sconfig.GetGameID()); + ActionReplay::LoadAndApplyCodes(globalIni, localIni, sconfig.GetGameID()); } } @@ -245,9 +245,6 @@ static void ApplyPatches(const Core::CPUThreadGuard& guard, const std::vector memory_patch_indices) { - if (AchievementManager::GetInstance().IsHardcoreModeActive()) - return; - std::lock_guard lock(s_on_frame_memory_mutex); for (std::size_t index : memory_patch_indices) { @@ -335,7 +332,7 @@ bool ApplyFramePatches(Core::System& system) void Shutdown() { s_on_frame.clear(); - ActionReplay::ApplyCodes({}); + ActionReplay::ApplyCodes({}, ""); Gecko::Shutdown(); } diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp index 93b9f35af3..75152c09ff 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp @@ -329,7 +329,6 @@ void AchievementSettingsWidget::ToggleProgress() void AchievementSettingsWidget::UpdateHardcoreMode() { - AchievementManager::GetInstance().SetHardcoreMode(); if (Config::Get(Config::RA_HARDCORE_ENABLED)) { Settings::Instance().SetDebugModeEnabled(false); diff --git a/Source/Core/DolphinQt/Config/ARCodeWidget.cpp b/Source/Core/DolphinQt/Config/ARCodeWidget.cpp index e7d44b8fe6..5089e98806 100644 --- a/Source/Core/DolphinQt/Config/ARCodeWidget.cpp +++ b/Source/Core/DolphinQt/Config/ARCodeWidget.cpp @@ -115,7 +115,7 @@ void ARCodeWidget::OnItemChanged(QListWidgetItem* item) m_ar_codes[m_code_list->row(item)].enabled = (item->checkState() == Qt::Checked); if (!m_restart_required) - ActionReplay::ApplyCodes(m_ar_codes); + ActionReplay::ApplyCodes(m_ar_codes, m_game_id); UpdateList(); SaveCodes(); diff --git a/Source/Core/DolphinQt/Config/GeckoCodeWidget.cpp b/Source/Core/DolphinQt/Config/GeckoCodeWidget.cpp index 3d2bd020e4..97a93051f5 100644 --- a/Source/Core/DolphinQt/Config/GeckoCodeWidget.cpp +++ b/Source/Core/DolphinQt/Config/GeckoCodeWidget.cpp @@ -202,7 +202,7 @@ void GeckoCodeWidget::OnItemChanged(QListWidgetItem* item) m_gecko_codes[index].enabled = (item->checkState() == Qt::Checked); if (!m_restart_required) - Gecko::SetActiveCodes(m_gecko_codes); + Gecko::SetActiveCodes(m_gecko_codes, m_game_id); SaveCodes(); } diff --git a/Source/Core/DolphinQt/Config/HardcoreWarningWidget.cpp b/Source/Core/DolphinQt/Config/HardcoreWarningWidget.cpp index eabec9a46e..257a43c366 100644 --- a/Source/Core/DolphinQt/Config/HardcoreWarningWidget.cpp +++ b/Source/Core/DolphinQt/Config/HardcoreWarningWidget.cpp @@ -35,7 +35,7 @@ void HardcoreWarningWidget::CreateWidgets() auto* icon = new QLabel; icon->setPixmap(warning_icon); - m_text = new QLabel(tr("This feature is disabled in hardcore mode.")); + m_text = new QLabel(tr("Only approved codes will be applied in hardcore mode.")); m_settings_button = new QPushButton(tr("Achievement Settings")); auto* layout = new QHBoxLayout; diff --git a/Source/Core/DolphinQt/Settings/GeneralPane.cpp b/Source/Core/DolphinQt/Settings/GeneralPane.cpp index 06715ceb65..0ab3b263a2 100644 --- a/Source/Core/DolphinQt/Settings/GeneralPane.cpp +++ b/Source/Core/DolphinQt/Settings/GeneralPane.cpp @@ -88,10 +88,9 @@ void GeneralPane::CreateLayout() void GeneralPane::OnEmulationStateChanged(Core::State state) { const bool running = state != Core::State::Uninitialized; - const bool hardcore = AchievementManager::GetInstance().IsHardcoreModeActive(); m_checkbox_dualcore->setEnabled(!running); - m_checkbox_cheats->setEnabled(!running && !hardcore); + m_checkbox_cheats->setEnabled(!running); m_checkbox_override_region_settings->setEnabled(!running); #ifdef USE_DISCORD_PRESENCE m_checkbox_discord_presence->setEnabled(!running); 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); + } +}