Skip to content

Commit

Permalink
Qt/Patches: Make WS/NI patches tri-state in the Patches screen, so di…
Browse files Browse the repository at this point in the history
…sabling 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.
  • Loading branch information
CookiePLMonster committed Dec 24, 2024
1 parent d67b662 commit d230df5
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 9 deletions.
79 changes: 71 additions & 8 deletions pcsx2-qt/Settings/GamePatchSettingsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
#include <algorithm>

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)
Expand All @@ -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);
}

Expand All @@ -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();
Expand All @@ -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);
Expand Down Expand Up @@ -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<Patch::PatchInfo> patches = Patch::GetPatchInfo(m_dialog->getSerial(), m_dialog->getDiscCRC(), false, showAllCRCS, &number_of_unlabeled_patches);
std::vector<std::string> 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<std::string> 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);
Expand All @@ -106,7 +133,7 @@ void GamePatchSettingsWidget::reloadList()
{
bool first = true;

for (Patch::PatchInfo& pi : patches)
for (const Patch::PatchInfo& pi : patches)
{
if (!first)
{
Expand All @@ -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);
}
}
Expand All @@ -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);
}
4 changes: 3 additions & 1 deletion pcsx2-qt/Settings/GamePatchSettingsWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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;
Expand Down
23 changes: 23 additions & 0 deletions pcsx2-qt/Settings/GamePatchSettingsWidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,29 @@
<property name="text">
<string>Any patches bundled with PCSX2 for this game will be disabled since you have unlabeled patches loaded.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="globalWsPatchState">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Widescreen patches are currently &lt;span style=&quot; font-weight:600;&quot;&gt;ENABLED&lt;/span&gt; globally.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="globalNiPatchState">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;No-Interlacing patches are currently &lt;span style=&quot; font-weight:600;&quot;&gt;ENABLED&lt;/span&gt; globally.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
Expand Down
20 changes: 20 additions & 0 deletions pcsx2/Patch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions pcsx2/Patch.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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

0 comments on commit d230df5

Please sign in to comment.