From d230df5121c8dfe85a1ff19547eea58d4f6fd172 Mon Sep 17 00:00:00 2001 From: Silent Date: Tue, 24 Dec 2024 17:09:44 +0100 Subject: [PATCH] Qt/Patches: Make WS/NI patches tri-state in the Patches screen, so disabling them per-game is possible This solves a long-standing issue where globally enabled widescreen/no-interlace patches visually appeared as disabled on the patches list, but they were in fact enabled and could NOT be disabled per-game. --- pcsx2-qt/Settings/GamePatchSettingsWidget.cpp | 79 +++++++++++++++++-- pcsx2-qt/Settings/GamePatchSettingsWidget.h | 4 +- pcsx2-qt/Settings/GamePatchSettingsWidget.ui | 23 ++++++ pcsx2/Patch.cpp | 20 +++++ pcsx2/Patch.h | 3 + 5 files changed, 120 insertions(+), 9 deletions(-) diff --git a/pcsx2-qt/Settings/GamePatchSettingsWidget.cpp b/pcsx2-qt/Settings/GamePatchSettingsWidget.cpp index 9ad3374f48aea..62bced91d668c 100644 --- a/pcsx2-qt/Settings/GamePatchSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GamePatchSettingsWidget.cpp @@ -16,7 +16,7 @@ #include GamePatchDetailsWidget::GamePatchDetailsWidget(std::string name, const std::string& author, - const std::string& description, bool enabled, SettingsWindow* dialog, QWidget* parent) + const std::string& description, bool tristate, Qt::CheckState checkState, SettingsWindow* dialog, QWidget* parent) : QWidget(parent) , m_dialog(dialog) , m_name(name) @@ -30,7 +30,8 @@ GamePatchDetailsWidget::GamePatchDetailsWidget(std::string name, const std::stri .arg(description.empty() ? tr("No description provided.") : QString::fromStdString(description))); pxAssert(dialog->getSettingsInterface()); - m_ui.enabled->setChecked(enabled); + m_ui.enabled->setTristate(tristate); + m_ui.enabled->setCheckState(checkState); connect(m_ui.enabled, &QCheckBox::checkStateChanged, this, &GamePatchDetailsWidget::onEnabledStateChanged); } @@ -40,9 +41,25 @@ void GamePatchDetailsWidget::onEnabledStateChanged(int state) { SettingsInterface* si = m_dialog->getSettingsInterface(); if (state == Qt::Checked) - si->AddToStringList("Patches", "Enable", m_name.c_str()); + { + si->AddToStringList(Patch::PATCHES_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY, m_name.c_str()); + si->RemoveFromStringList(Patch::PATCHES_CONFIG_SECTION, Patch::PATCH_DISABLE_CONFIG_KEY, m_name.c_str()); + } else - si->RemoveFromStringList("Patches", "Enable", m_name.c_str()); + { + si->RemoveFromStringList(Patch::PATCHES_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY, m_name.c_str()); + if (m_ui.enabled->isTristate()) + { + if (state == Qt::Unchecked) + { + si->AddToStringList(Patch::PATCHES_CONFIG_SECTION, Patch::PATCH_DISABLE_CONFIG_KEY, m_name.c_str()); + } + else + { + si->RemoveFromStringList(Patch::PATCHES_CONFIG_SECTION, Patch::PATCH_DISABLE_CONFIG_KEY, m_name.c_str()); + } + } + } si->Save(); g_emu_thread->reloadGameSettings(); @@ -56,6 +73,8 @@ GamePatchSettingsWidget::GamePatchSettingsWidget(SettingsWindow* dialog, QWidget m_ui.scrollArea->setFrameShadow(QFrame::Sunken); setUnlabeledPatchesWarningVisibility(false); + setGlobalWsPatchNoteVisibility(false); + setGlobalNiPatchNoteVisibility(false); SettingsInterface* sif = m_dialog->getSettingsInterface(); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.allCRCsCheckbox, "EmuCore", "ShowPatchesForAllCRCs", false); @@ -88,14 +107,22 @@ void GamePatchSettingsWidget::disableAllPatches() void GamePatchSettingsWidget::reloadList() { + const SettingsInterface* si = m_dialog->getSettingsInterface(); // Patches shouldn't have any unlabelled patch groups, because they're new. u32 number_of_unlabeled_patches = 0; bool showAllCRCS = m_ui.allCRCsCheckbox->isChecked(); std::vector patches = Patch::GetPatchInfo(m_dialog->getSerial(), m_dialog->getDiscCRC(), false, showAllCRCS, &number_of_unlabeled_patches); std::vector enabled_list = - m_dialog->getSettingsInterface()->GetStringList(Patch::PATCHES_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY); + si->GetStringList(Patch::PATCHES_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY); + std::vector disabled_list = + si->GetStringList(Patch::PATCHES_CONFIG_SECTION, Patch::PATCH_DISABLE_CONFIG_KEY); + + const bool ws_patches_enabled_globally = m_dialog->getEffectiveBoolValue("EmuCore", "EnableWideScreenPatches", false); + const bool ni_patches_enabled_globally = m_dialog->getEffectiveBoolValue("EmuCore", "EnableNoInterlacingPatches", false); setUnlabeledPatchesWarningVisibility(number_of_unlabeled_patches > 0); + setGlobalWsPatchNoteVisibility(ws_patches_enabled_globally); + setGlobalNiPatchNoteVisibility(ni_patches_enabled_globally); delete m_ui.scrollArea->takeWidget(); QWidget* container = new QWidget(m_ui.scrollArea); @@ -106,7 +133,7 @@ void GamePatchSettingsWidget::reloadList() { bool first = true; - for (Patch::PatchInfo& pi : patches) + for (const Patch::PatchInfo& pi : patches) { if (!first) { @@ -120,9 +147,35 @@ void GamePatchSettingsWidget::reloadList() first = false; } - const bool enabled = (std::find(enabled_list.begin(), enabled_list.end(), pi.name) != enabled_list.end()); + const bool is_on_enable_list = std::find(enabled_list.begin(), enabled_list.end(), pi.name) != enabled_list.end(); + const bool is_on_disable_list = std::find(disabled_list.begin(), disabled_list.end(), pi.name) != disabled_list.end(); + const bool globally_toggleable_option = Patch::IsGloballyToggleablePatch(pi); + + Qt::CheckState check_state; + if (!globally_toggleable_option) + { + // Normal patches + check_state = is_on_enable_list && !is_on_disable_list ? Qt::CheckState::Checked : Qt::CheckState::Unchecked; + } + else + { + // WS/NI patches + if (is_on_disable_list) + { + check_state = Qt::CheckState::Unchecked; + } + else if (is_on_enable_list) + { + check_state = Qt::CheckState::Checked; + } + else + { + check_state = Qt::CheckState::PartiallyChecked; + } + } + GamePatchDetailsWidget* it = - new GamePatchDetailsWidget(std::move(pi.name), pi.author, pi.description, enabled, m_dialog, container); + new GamePatchDetailsWidget(std::move(pi.name), pi.author, pi.description, globally_toggleable_option, check_state, m_dialog, container); layout->addWidget(it); } } @@ -141,3 +194,13 @@ void GamePatchSettingsWidget::setUnlabeledPatchesWarningVisibility(bool visible) { m_ui.unlabeledPatchWarning->setVisible(visible); } + +void GamePatchSettingsWidget::setGlobalWsPatchNoteVisibility(bool visible) +{ + m_ui.globalWsPatchState->setVisible(visible); +} + +void GamePatchSettingsWidget::setGlobalNiPatchNoteVisibility(bool visible) +{ + m_ui.globalNiPatchState->setVisible(visible); +} diff --git a/pcsx2-qt/Settings/GamePatchSettingsWidget.h b/pcsx2-qt/Settings/GamePatchSettingsWidget.h index 53d46771f68a7..889e3f502e49b 100644 --- a/pcsx2-qt/Settings/GamePatchSettingsWidget.h +++ b/pcsx2-qt/Settings/GamePatchSettingsWidget.h @@ -22,7 +22,7 @@ class GamePatchDetailsWidget : public QWidget Q_OBJECT public: - GamePatchDetailsWidget(std::string name, const std::string& author, const std::string& description, bool enabled, + GamePatchDetailsWidget(std::string name, const std::string& author, const std::string& description, bool tristate, Qt::CheckState checkState, SettingsWindow* dialog, QWidget* parent); ~GamePatchDetailsWidget(); @@ -50,6 +50,8 @@ private Q_SLOTS: private: void reloadList(); void setUnlabeledPatchesWarningVisibility(bool visible); + void setGlobalWsPatchNoteVisibility(bool visible); + void setGlobalNiPatchNoteVisibility(bool visible); Ui::GamePatchSettingsWidget m_ui; SettingsWindow* m_dialog; diff --git a/pcsx2-qt/Settings/GamePatchSettingsWidget.ui b/pcsx2-qt/Settings/GamePatchSettingsWidget.ui index 39871b89d5845..7def1249296e4 100644 --- a/pcsx2-qt/Settings/GamePatchSettingsWidget.ui +++ b/pcsx2-qt/Settings/GamePatchSettingsWidget.ui @@ -38,6 +38,29 @@ Any patches bundled with PCSX2 for this game will be disabled since you have unlabeled patches loaded. + + true + + + + + + + <html><head/><body><p>Widescreen patches are currently <span style=" font-weight:600;">ENABLED</span> globally.</p></body></html> + + + true + + + + + + + <html><head/><body><p>No-Interlacing patches are currently <span style=" font-weight:600;">ENABLED</span> globally.</p></body></html> + + + true + diff --git a/pcsx2/Patch.cpp b/pcsx2/Patch.cpp index 599bd74024a9f..a0a34e86b308b 100644 --- a/pcsx2/Patch.cpp +++ b/pcsx2/Patch.cpp @@ -171,6 +171,7 @@ namespace Patch const char* PATCHES_CONFIG_SECTION = "Patches"; const char* CHEATS_CONFIG_SECTION = "Cheats"; const char* PATCH_ENABLE_CONFIG_KEY = "Enable"; + const char* PATCH_DISABLE_CONFIG_KEY = "Disable"; static zip_t* s_patches_zip; static PatchList s_gamedb_patches; @@ -589,6 +590,8 @@ void Patch::ReloadEnabledLists() s_enabled_patches = Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY); + const EnablePatchList disabled_patches = Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_DISABLE_CONFIG_KEY); + // Name based matching for widescreen/NI settings. if (EmuConfig.EnableWideScreenPatches) { @@ -606,6 +609,18 @@ void Patch::ReloadEnabledLists() s_enabled_patches.emplace_back(NI_PATCH_NAME); } } + + for (auto it = s_enabled_patches.begin(); it != s_enabled_patches.end();) + { + if (std::find(disabled_patches.begin(), disabled_patches.end(), *it) != disabled_patches.end()) + { + it = s_enabled_patches.erase(it); + } + else + { + ++it; + } + } } u32 Patch::EnablePatches(const PatchList& patches, const EnablePatchList& enable_list) @@ -1027,6 +1042,11 @@ void Patch::ApplyLoadedPatches(patch_place_type place) } } +bool Patch::IsGloballyToggleablePatch(const PatchInfo& patch_info) +{ + return patch_info.name == WS_PATCH_NAME || patch_info.name == NI_PATCH_NAME; +} + void Patch::ApplyDynamicPatches(u32 pc) { for (const auto& dynpatch : s_active_pnach_dynamic_patches) diff --git a/pcsx2/Patch.h b/pcsx2/Patch.h index 34d3e25801ba7..a572aed7c0045 100644 --- a/pcsx2/Patch.h +++ b/pcsx2/Patch.h @@ -78,6 +78,7 @@ namespace Patch extern const char* PATCHES_CONFIG_SECTION; extern const char* CHEATS_CONFIG_SECTION; extern const char* PATCH_ENABLE_CONFIG_KEY; + extern const char* PATCH_DISABLE_CONFIG_KEY; extern PatchInfoList GetPatchInfo(const std::string_view serial, u32 crc, bool cheats, bool showAllCRCS, u32* num_unlabelled_patches); @@ -103,4 +104,6 @@ namespace Patch // and then it loads only the ones which are enabled according to the current config // (this happens at AppCoreThread::ApplySettings(...) ) extern void ApplyLoadedPatches(patch_place_type place); + + extern bool IsGloballyToggleablePatch(const PatchInfo& patch_info); } // namespace Patch \ No newline at end of file