From f3b8a985e77e0d5157bc08708a470fd8394bb818 Mon Sep 17 00:00:00 2001 From: smurf3tte <75271109+smurf3tte@users.noreply.github.com> Date: Tue, 29 Dec 2020 14:24:46 -0800 Subject: [PATCH 1/2] Patches for Resident Evil 2/3 audio issues These games are erroneously zeroing buffers before they can be fully copied to ARAM by DMA. The responsible memset() calls are followed by a call to DVDRead() which issues dcbi instructions that effectively cancel the memset() on real hardware. Because Dolphin lacks dcache emulation, the effects of the memset() calls are observed, which causes missing audio. In a comment on the original bug, phire noted that the issue can be corrected by simply nop'ing out the offending memset() calls. Because the games dynamically load different .rel executables based on the character and/or language, the addresses of these calls can vary. To deal generally with the problem of code being dynamically loaded to fixed, known addresses, the patch engine is extended to support conditional patches which require a match against a known value. This sort of thing is already achievable with Action Replay/Gecko codes, but their use depends on enabling cheats globally in Dolphin, which is not a prerequisite shared by patches. Patches are included for every region, character, and language combination. They are enabled by default. The end result is an approximation of the games' behavior on real hardware without the associated complexity of proper dcache emulation. https://bugs.dolphin-emu.org/issues/9840 --- Data/Sys/GameSettings/GHAE08.ini | 16 +++ Data/Sys/GameSettings/GHAJ08.ini | 16 +++ Data/Sys/GameSettings/GHAP08.ini | 32 ++++++ Data/Sys/GameSettings/GLEE08.ini | 12 +++ Data/Sys/GameSettings/GLEJ08.ini | 12 +++ Data/Sys/GameSettings/GLEP08.ini | 20 ++++ Source/Core/Core/PatchEngine.cpp | 15 ++- Source/Core/Core/PatchEngine.h | 2 + .../Core/DolphinQt/Config/NewPatchDialog.cpp | 100 +++++++++++------- .../Core/DolphinQt/Config/PatchesWidget.cpp | 15 ++- 10 files changed, 196 insertions(+), 44 deletions(-) create mode 100644 Data/Sys/GameSettings/GHAE08.ini create mode 100644 Data/Sys/GameSettings/GHAJ08.ini create mode 100644 Data/Sys/GameSettings/GHAP08.ini create mode 100644 Data/Sys/GameSettings/GLEE08.ini create mode 100644 Data/Sys/GameSettings/GLEJ08.ini create mode 100644 Data/Sys/GameSettings/GLEP08.ini diff --git a/Data/Sys/GameSettings/GHAE08.ini b/Data/Sys/GameSettings/GHAE08.ini new file mode 100644 index 0000000000..373cbb45e3 --- /dev/null +++ b/Data/Sys/GameSettings/GHAE08.ini @@ -0,0 +1,16 @@ +# GHAE08 - Resident Evil 2 + +[OnFrame] +# Work around a game bug that causes background sounds to be zeroed during load. +# The bug was masked on real hardware by dcache. This patch fully fixes the bug +# and should also work on real hardware. Dolphin doesn't emulate dcache because +# the performance hit would be huge. +$Fix audio issues +# main.dol +0x800339E4:dword:0x60000000 +# leon.rel +0x8055ACBC:dword:0x60000000:0x4BAA8445 +# claire.rel +0x8055AB54:dword:0x60000000:0x4BAA85AD +[OnFrame_Enabled] +$Fix audio issues diff --git a/Data/Sys/GameSettings/GHAJ08.ini b/Data/Sys/GameSettings/GHAJ08.ini new file mode 100644 index 0000000000..9bb3feec95 --- /dev/null +++ b/Data/Sys/GameSettings/GHAJ08.ini @@ -0,0 +1,16 @@ +# GHAJ08 - Biohazard 2 + +[OnFrame] +# Work around a game bug that causes background sounds to be zeroed during load. +# The bug was masked on real hardware by dcache. This patch fully fixes the bug +# and should also work on real hardware. Dolphin doesn't emulate dcache because +# the performance hit would be huge. +$Fix audio issues +# main.dol +0x80065FFC:dword:0x60000000 +# leon.rel +0x805C5CC4:dword:0x60000000:0x4BA3D43D +# claire.rel +0x805C5BFC:dword:0x60000000:0x4BA3D505 +[OnFrame_Enabled] +$Fix audio issues diff --git a/Data/Sys/GameSettings/GHAP08.ini b/Data/Sys/GameSettings/GHAP08.ini new file mode 100644 index 0000000000..d6e39d5c17 --- /dev/null +++ b/Data/Sys/GameSettings/GHAP08.ini @@ -0,0 +1,32 @@ +# GHAP08 - Resident Evil 2 + +[OnFrame] +# Work around a game bug that causes background sounds to be zeroed during load. +# The bug was masked on real hardware by dcache. This patch fully fixes the bug +# and should also work on real hardware. Dolphin doesn't emulate dcache because +# the performance hit would be huge. +$Fix audio issues +# main.dol +0x80033D60:dword:0x60000000 +# leon.rel +0x8055C5F8:dword:0x60000000:0x4BAA6B09 +# claire.rel +0x8055C490:dword:0x60000000:0x4BAA6C71 +# leon_g.rel +0x8055C3B8:dword:0x60000000:0x4BAA6D49 +# claire_g.rel +0x8055C328:dword:0x60000000:0x4BAA6DD9 +# leon_f.rel +0x8055D188:dword:0x60000000:0x4BAA5F79 +# claire_f.rel +0x8055D068:dword:0x60000000:0x4BAA6099 +# leon_s.rel +0x8055D100:dword:0x60000000:0x4BAA6001 +# claire_s.rel +0x8055D064:dword:0x60000000:0x4BAA609D +# leon_i.rel +0x8055CFDC:dword:0x60000000:0x4BAA6125 +# claire_i.rel +0x8055CEBC:dword:0x60000000:0x4BAA6245 +[OnFrame_Enabled] +$Fix audio issues diff --git a/Data/Sys/GameSettings/GLEE08.ini b/Data/Sys/GameSettings/GLEE08.ini new file mode 100644 index 0000000000..90759a99a9 --- /dev/null +++ b/Data/Sys/GameSettings/GLEE08.ini @@ -0,0 +1,12 @@ +# GLEE08 - Resident Evil 3: Nemesis + +[OnFrame] +# Work around a game bug that causes background sounds to be zeroed during load. +# The bug was masked on real hardware by dcache. This patch fully fixes the bug +# and should also work on real hardware. Dolphin doesn't emulate dcache because +# the performance hit would be huge. +$Fix audio issues +# main.dol +0x80150E94:dword:0x60000000 +[OnFrame_Enabled] +$Fix audio issues diff --git a/Data/Sys/GameSettings/GLEJ08.ini b/Data/Sys/GameSettings/GLEJ08.ini new file mode 100644 index 0000000000..5c8ebc84fc --- /dev/null +++ b/Data/Sys/GameSettings/GLEJ08.ini @@ -0,0 +1,12 @@ +# GLEJ08 - BioHazard 3: Last Escape + +[OnFrame] +# Work around a game bug that causes background sounds to be zeroed during load. +# The bug was masked on real hardware by dcache. This patch fully fixes the bug +# and should also work on real hardware. Dolphin doesn't emulate dcache because +# the performance hit would be huge. +$Fix audio issues +# main.dol +0x8015110C:dword:0x60000000 +[OnFrame_Enabled] +$Fix audio issues diff --git a/Data/Sys/GameSettings/GLEP08.ini b/Data/Sys/GameSettings/GLEP08.ini new file mode 100644 index 0000000000..9c947103ac --- /dev/null +++ b/Data/Sys/GameSettings/GLEP08.ini @@ -0,0 +1,20 @@ +# GLEP08 - Resident Evil 3: Nemesis + +[OnFrame] +# Work around a game bug that causes background sounds to be zeroed during load. +# The bug was masked on real hardware by dcache. This patch fully fixes the bug +# and should also work on real hardware. Dolphin doesn't emulate dcache because +# the performance hit would be huge. +$Fix audio issues +# eng.rel +0x8058C174:dword:0x60000000:0x4BA76F8D +# ger.rel +0x8058CE40:dword:0x60000000:0x4BA762C1 +# fra.rel +0x8058D03C:dword:0x60000000:0x4BA760C5 +# spa.rel +0x8058D024:dword:0x60000000:0x4BA760DD +# ita.rel +0x8058CEA4:dword:0x60000000:0x4BA7625D +[OnFrame_Enabled] +$Fix audio issues diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp index 4f4daafcd7..a3b13c14d3 100644 --- a/Source/Core/Core/PatchEngine.cpp +++ b/Source/Core/Core/PatchEngine.cpp @@ -90,6 +90,11 @@ void LoadPatchSection(const std::string& section, std::vector& patches, I bool success = true; success &= TryParse(items[0], &pE.address); success &= TryParse(items[2], &pE.value); + if (items.size() >= 4) + { + success &= TryParse(items[3], &pE.comparand); + pE.conditional = true; + } const auto iter = std::find(s_patch_type_strings.begin(), s_patch_type_strings.end(), items[1]); @@ -184,16 +189,20 @@ static void ApplyPatches(const std::vector& patches) { u32 addr = entry.address; u32 value = entry.value; + u32 comparand = entry.comparand; switch (entry.type) { case PatchType::Patch8Bit: - PowerPC::HostWrite_U8(static_cast(value), addr); + if (!entry.conditional || PowerPC::HostRead_U8(addr) == static_cast(comparand)) + PowerPC::HostWrite_U8(static_cast(value), addr); break; case PatchType::Patch16Bit: - PowerPC::HostWrite_U16(static_cast(value), addr); + if (!entry.conditional || PowerPC::HostRead_U16(addr) == static_cast(comparand)) + PowerPC::HostWrite_U16(static_cast(value), addr); break; case PatchType::Patch32Bit: - PowerPC::HostWrite_U32(value, addr); + if (!entry.conditional || PowerPC::HostRead_U32(addr) == comparand) + PowerPC::HostWrite_U32(value, addr); break; default: // unknown patchtype diff --git a/Source/Core/Core/PatchEngine.h b/Source/Core/Core/PatchEngine.h index a496f04936..490fb2e646 100644 --- a/Source/Core/Core/PatchEngine.h +++ b/Source/Core/Core/PatchEngine.h @@ -27,6 +27,8 @@ struct PatchEntry PatchType type = PatchType::Patch8Bit; u32 address = 0; u32 value = 0; + u32 comparand = 0; + bool conditional = false; }; struct Patch diff --git a/Source/Core/DolphinQt/Config/NewPatchDialog.cpp b/Source/Core/DolphinQt/Config/NewPatchDialog.cpp index b0f45afc49..95d0eb7621 100644 --- a/Source/Core/DolphinQt/Config/NewPatchDialog.cpp +++ b/Source/Core/DolphinQt/Config/NewPatchDialog.cpp @@ -4,6 +4,7 @@ #include "DolphinQt/Config/NewPatchDialog.h" +#include #include #include #include @@ -84,6 +85,25 @@ void NewPatchDialog::AddEntry() m_entry_layout->addWidget(CreateEntry(m_patch.entries[m_patch.entries.size() - 1])); } +static u32 OnTextEdited(QLineEdit* edit, const QString& text) +{ + bool okay = false; + u32 value = text.toUInt(&okay, 16); + + QFont font; + QPalette palette; + + font.setBold(!okay); + + if (!okay) + palette.setColor(QPalette::Text, Qt::red); + + edit->setFont(font); + edit->setPalette(palette); + + return value; +} + static bool PatchEq(const PatchEngine::PatchEntry& a, const PatchEngine::PatchEntry& b) { if (a.address != b.address) @@ -95,6 +115,12 @@ static bool PatchEq(const PatchEngine::PatchEntry& a, const PatchEngine::PatchEn if (a.value != b.value) return false; + if (a.comparand != b.comparand) + return false; + + if (a.conditional != b.conditional) + return false; + return true; } @@ -115,56 +141,41 @@ QGroupBox* NewPatchDialog::CreateEntry(PatchEngine::PatchEntry& entry) type_layout->addWidget(dword); type->setLayout(type_layout); - auto* offset = new QLineEdit; + auto* address = new QLineEdit; auto* value = new QLineEdit; + auto* comparand = new QLineEdit; - m_edits.push_back(offset); + m_edits.push_back(address); m_edits.push_back(value); + m_edits.push_back(comparand); + + auto* conditional = new QCheckBox(tr("Conditional")); + auto* comparand_label = new QLabel(tr("Comparand:")); auto* layout = new QGridLayout; layout->addWidget(type, 0, 0, 1, -1); - layout->addWidget(new QLabel(tr("Offset:")), 1, 0); - layout->addWidget(offset, 1, 1); + layout->addWidget(new QLabel(tr("Address:")), 1, 0); + layout->addWidget(address, 1, 1); layout->addWidget(new QLabel(tr("Value:")), 2, 0); layout->addWidget(value, 2, 1); - layout->addWidget(remove, 3, 0, 1, -1); + layout->addWidget(conditional, 3, 0, 1, -1); + layout->addWidget(comparand_label, 4, 0); + layout->addWidget(comparand, 4, 1); + layout->addWidget(remove, 5, 0, 1, -1); box->setLayout(layout); - connect(offset, qOverload(&QLineEdit::textEdited), - [&entry, offset](const QString& text) { - bool okay = true; - entry.address = text.toUInt(&okay, 16); - - QFont font; - QPalette palette; - - font.setBold(!okay); - - if (!okay) - palette.setColor(QPalette::Text, Qt::red); - - offset->setFont(font); - offset->setPalette(palette); - }); + connect(address, qOverload(&QLineEdit::textEdited), + [&entry, address](const QString& text) { entry.address = OnTextEdited(address, text); }); connect(value, qOverload(&QLineEdit::textEdited), - [&entry, value](const QString& text) { - bool okay; - entry.value = text.toUInt(&okay, 16); + [&entry, value](const QString& text) { entry.value = OnTextEdited(value, text); }); - QFont font; - QPalette palette; - - font.setBold(!okay); - - if (!okay) - palette.setColor(QPalette::Text, Qt::red); - - value->setFont(font); - value->setPalette(palette); + connect(comparand, qOverload(&QLineEdit::textEdited), + [&entry, comparand](const QString& text) { + entry.comparand = OnTextEdited(comparand, text); }); - connect(remove, &QPushButton::clicked, [this, box, offset, value, entry] { + connect(remove, &QPushButton::clicked, [this, box, address, value, comparand, entry] { if (m_patch.entries.size() > 1) { box->setVisible(false); @@ -175,9 +186,9 @@ QGroupBox* NewPatchDialog::CreateEntry(PatchEngine::PatchEntry& entry) std::find_if(m_patch.entries.begin(), m_patch.entries.end(), [entry](const PatchEngine::PatchEntry& e) { return PatchEq(e, entry); })); - const auto it = - std::remove_if(m_edits.begin(), m_edits.end(), [offset, value](QLineEdit* line_edit) { - return line_edit == offset || line_edit == value; + const auto it = std::remove_if( + m_edits.begin(), m_edits.end(), [address, value, comparand](QLineEdit* line_edit) { + return line_edit == address || line_edit == value || line_edit == comparand; }); m_edits.erase(it, m_edits.end()); } @@ -202,8 +213,19 @@ QGroupBox* NewPatchDialog::CreateEntry(PatchEngine::PatchEntry& entry) word->setChecked(entry.type == PatchEngine::PatchType::Patch16Bit); dword->setChecked(entry.type == PatchEngine::PatchType::Patch32Bit); - offset->setText(QStringLiteral("%1").arg(entry.address, 8, 16, QLatin1Char('0'))); + connect(conditional, &QCheckBox::toggled, [&entry, comparand_label, comparand](bool checked) { + entry.conditional = checked; + comparand_label->setVisible(checked); + comparand->setVisible(checked); + }); + + conditional->setChecked(entry.conditional); + comparand_label->setVisible(entry.conditional); + comparand->setVisible(entry.conditional); + + address->setText(QStringLiteral("%1").arg(entry.address, 8, 16, QLatin1Char('0'))); value->setText(QStringLiteral("%1").arg(entry.value, 8, 16, QLatin1Char('0'))); + comparand->setText(QStringLiteral("%1").arg(entry.comparand, 8, 16, QLatin1Char('0'))); return box; } diff --git a/Source/Core/DolphinQt/Config/PatchesWidget.cpp b/Source/Core/DolphinQt/Config/PatchesWidget.cpp index 43dd4a415e..8628f1997a 100644 --- a/Source/Core/DolphinQt/Config/PatchesWidget.cpp +++ b/Source/Core/DolphinQt/Config/PatchesWidget.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include "Common/FileUtil.h" #include "Common/IniFile.h" #include "Common/StringUtil.h" @@ -143,8 +145,17 @@ void PatchesWidget::SavePatches() for (const auto& entry : patch.entries) { - lines.emplace_back(StringFromFormat("0x%08X:%s:0x%08X", entry.address, - PatchEngine::PatchTypeAsString(entry.type), entry.value)); + if (!entry.conditional) + { + lines.emplace_back(fmt::format("0x{:08X}:{}:0x{:08X}", entry.address, + PatchEngine::PatchTypeAsString(entry.type), entry.value)); + } + else + { + lines.emplace_back(fmt::format("0x{:08X}:{}:0x{:08X}:0x{:08X}", entry.address, + PatchEngine::PatchTypeAsString(entry.type), entry.value, + entry.comparand)); + } } } From f4c579e720013065ebe5257aa40d713e354bcfce Mon Sep 17 00:00:00 2001 From: smurf3tte <75271109+smurf3tte@users.noreply.github.com> Date: Tue, 29 Dec 2020 14:28:27 -0800 Subject: [PATCH 2/2] Fix bad memory references in NewPatchDialog This code was storing references to patch entries which could move around in memory if a patch was erased from the middle of a vector or if the vector itself was reallocated. Instead, NewPatchDialog maintains a separate copy of the patch entries which are committed back to the patch if the user accepts the changes. --- .../Core/DolphinQt/Config/NewPatchDialog.cpp | 126 ++++++++++-------- Source/Core/DolphinQt/Config/NewPatchDialog.h | 8 +- 2 files changed, 76 insertions(+), 58 deletions(-) diff --git a/Source/Core/DolphinQt/Config/NewPatchDialog.cpp b/Source/Core/DolphinQt/Config/NewPatchDialog.cpp index 95d0eb7621..6f06899639 100644 --- a/Source/Core/DolphinQt/Config/NewPatchDialog.cpp +++ b/Source/Core/DolphinQt/Config/NewPatchDialog.cpp @@ -18,6 +18,23 @@ #include "Core/PatchEngine.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" +struct NewPatchEntry +{ + NewPatchEntry() = default; + + // These entries share the lifetime of their associated text widgets, and because they are + // captured by pointer by various edit handlers, they should not copied or moved. + NewPatchEntry(const NewPatchEntry&) = delete; + NewPatchEntry& operator=(const NewPatchEntry&) = delete; + NewPatchEntry(NewPatchEntry&&) = delete; + NewPatchEntry& operator=(NewPatchEntry&&) = delete; + + QLineEdit* address = nullptr; + QLineEdit* value = nullptr; + QLineEdit* comparand = nullptr; + PatchEngine::PatchEntry entry; +}; + NewPatchDialog::NewPatchDialog(QWidget* parent, PatchEngine::Patch& patch) : QDialog(parent), m_patch(patch) { @@ -39,6 +56,8 @@ NewPatchDialog::NewPatchDialog(QWidget* parent, PatchEngine::Patch& patch) } } +NewPatchDialog::~NewPatchDialog() = default; + void NewPatchDialog::CreateWidgets() { m_name_edit = new QLineEdit; @@ -80,9 +99,7 @@ void NewPatchDialog::ConnectWidgets() void NewPatchDialog::AddEntry() { - m_patch.entries.emplace_back(); - - m_entry_layout->addWidget(CreateEntry(m_patch.entries[m_patch.entries.size() - 1])); + m_entry_layout->addWidget(CreateEntry({})); } static u32 OnTextEdited(QLineEdit* edit, const QString& text) @@ -104,27 +121,7 @@ static u32 OnTextEdited(QLineEdit* edit, const QString& text) return value; } -static bool PatchEq(const PatchEngine::PatchEntry& a, const PatchEngine::PatchEntry& b) -{ - if (a.address != b.address) - return false; - - if (a.type != b.type) - return false; - - if (a.value != b.value) - return false; - - if (a.comparand != b.comparand) - return false; - - if (a.conditional != b.conditional) - return false; - - return true; -} - -QGroupBox* NewPatchDialog::CreateEntry(PatchEngine::PatchEntry& entry) +QGroupBox* NewPatchDialog::CreateEntry(const PatchEngine::PatchEntry& entry) { QGroupBox* box = new QGroupBox(); @@ -145,9 +142,11 @@ QGroupBox* NewPatchDialog::CreateEntry(PatchEngine::PatchEntry& entry) auto* value = new QLineEdit; auto* comparand = new QLineEdit; - m_edits.push_back(address); - m_edits.push_back(value); - m_edits.push_back(comparand); + auto* new_entry = m_entries.emplace_back(std::make_unique()).get(); + new_entry->address = address; + new_entry->value = value; + new_entry->comparand = comparand; + new_entry->entry = entry; auto* conditional = new QCheckBox(tr("Conditional")); auto* comparand_label = new QLabel(tr("Comparand:")); @@ -165,56 +164,53 @@ QGroupBox* NewPatchDialog::CreateEntry(PatchEngine::PatchEntry& entry) box->setLayout(layout); connect(address, qOverload(&QLineEdit::textEdited), - [&entry, address](const QString& text) { entry.address = OnTextEdited(address, text); }); - - connect(value, qOverload(&QLineEdit::textEdited), - [&entry, value](const QString& text) { entry.value = OnTextEdited(value, text); }); - - connect(comparand, qOverload(&QLineEdit::textEdited), - [&entry, comparand](const QString& text) { - entry.comparand = OnTextEdited(comparand, text); + [new_entry](const QString& text) { + new_entry->entry.address = OnTextEdited(new_entry->address, text); }); - connect(remove, &QPushButton::clicked, [this, box, address, value, comparand, entry] { - if (m_patch.entries.size() > 1) + connect(value, qOverload(&QLineEdit::textEdited), + [new_entry](const QString& text) { + new_entry->entry.value = OnTextEdited(new_entry->value, text); + }); + + connect(comparand, qOverload(&QLineEdit::textEdited), + [new_entry](const QString& text) { + new_entry->entry.comparand = OnTextEdited(new_entry->comparand, text); + }); + + connect(remove, &QPushButton::clicked, [this, box, new_entry] { + if (m_entries.size() > 1) { box->setVisible(false); m_entry_layout->removeWidget(box); box->deleteLater(); - m_patch.entries.erase( - std::find_if(m_patch.entries.begin(), m_patch.entries.end(), - [entry](const PatchEngine::PatchEntry& e) { return PatchEq(e, entry); })); - - const auto it = std::remove_if( - m_edits.begin(), m_edits.end(), [address, value, comparand](QLineEdit* line_edit) { - return line_edit == address || line_edit == value || line_edit == comparand; - }); - m_edits.erase(it, m_edits.end()); + m_entries.erase(std::find_if(m_entries.begin(), m_entries.end(), + [new_entry](const auto& e) { return e.get() == new_entry; })); } }); - connect(byte, &QRadioButton::toggled, [&entry](bool checked) { + connect(byte, &QRadioButton::toggled, [new_entry](bool checked) { if (checked) - entry.type = PatchEngine::PatchType::Patch8Bit; + new_entry->entry.type = PatchEngine::PatchType::Patch8Bit; }); - connect(word, &QRadioButton::toggled, [&entry](bool checked) { + connect(word, &QRadioButton::toggled, [new_entry](bool checked) { if (checked) - entry.type = PatchEngine::PatchType::Patch16Bit; + new_entry->entry.type = PatchEngine::PatchType::Patch16Bit; }); - connect(dword, &QRadioButton::toggled, [&entry](bool checked) { + connect(dword, &QRadioButton::toggled, [new_entry](bool checked) { if (checked) - entry.type = PatchEngine::PatchType::Patch32Bit; + new_entry->entry.type = PatchEngine::PatchType::Patch32Bit; }); byte->setChecked(entry.type == PatchEngine::PatchType::Patch8Bit); word->setChecked(entry.type == PatchEngine::PatchType::Patch16Bit); dword->setChecked(entry.type == PatchEngine::PatchType::Patch32Bit); - connect(conditional, &QCheckBox::toggled, [&entry, comparand_label, comparand](bool checked) { - entry.conditional = checked; + connect(conditional, &QCheckBox::toggled, [new_entry, comparand_label, comparand](bool checked) { + new_entry->entry.conditional = checked; comparand_label->setVisible(checked); comparand->setVisible(checked); }); @@ -240,11 +236,22 @@ void NewPatchDialog::accept() bool valid = true; - for (const auto* edit : m_edits) + for (const auto& entry : m_entries) { - edit->text().toUInt(&valid, 16); + entry->address->text().toUInt(&valid, 16); if (!valid) break; + + entry->value->text().toUInt(&valid, 16); + if (!valid) + break; + + if (entry->entry.conditional) + { + entry->comparand->text().toUInt(&valid, 16); + if (!valid) + break; + } } if (!valid) @@ -255,5 +262,12 @@ void NewPatchDialog::accept() return; } + m_patch.entries.clear(); + + for (const auto& entry : m_entries) + { + m_patch.entries.emplace_back(entry->entry); + } + QDialog::accept(); } diff --git a/Source/Core/DolphinQt/Config/NewPatchDialog.h b/Source/Core/DolphinQt/Config/NewPatchDialog.h index d77005cc1d..ff9d09feea 100644 --- a/Source/Core/DolphinQt/Config/NewPatchDialog.h +++ b/Source/Core/DolphinQt/Config/NewPatchDialog.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include @@ -21,10 +22,13 @@ class QLineEdit; class QVBoxLayout; class QPushButton; +struct NewPatchEntry; + class NewPatchDialog : public QDialog { public: explicit NewPatchDialog(QWidget* parent, PatchEngine::Patch& patch); + ~NewPatchDialog() override; private: void CreateWidgets(); @@ -33,7 +37,7 @@ private: void accept() override; - QGroupBox* CreateEntry(PatchEngine::PatchEntry& entry); + QGroupBox* CreateEntry(const PatchEngine::PatchEntry& entry); QLineEdit* m_name_edit; QWidget* m_entry_widget; @@ -41,7 +45,7 @@ private: QPushButton* m_add_button; QDialogButtonBox* m_button_box; - std::vector m_edits; + std::vector> m_entries; PatchEngine::Patch& m_patch; };