From fb96ecb7da563d8b66ce4083db239da402e3aebc Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 21 Jun 2021 19:56:27 +0200 Subject: [PATCH 1/2] Move patch saving code to PatchEngine --- Source/Core/Core/PatchEngine.cpp | 53 ++++++++++++++++--- Source/Core/Core/PatchEngine.h | 5 +- .../Core/DolphinQt/Config/PatchesWidget.cpp | 44 ++------------- 3 files changed, 54 insertions(+), 48 deletions(-) diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp index 0a815b37b1..afd20df61c 100644 --- a/Source/Core/Core/PatchEngine.cpp +++ b/Source/Core/Core/PatchEngine.cpp @@ -14,6 +14,8 @@ #include #include +#include + #include "Common/Assert.h" #include "Common/IniFile.h" #include "Common/StringUtil.h" @@ -43,8 +45,8 @@ const char* PatchTypeAsString(PatchType type) return s_patch_type_strings.at(static_cast(type)); } -void LoadPatchSection(const std::string& section, std::vector& patches, IniFile& globalIni, - IniFile& localIni) +void LoadPatchSection(const std::string& section, std::vector* patches, + const IniFile& globalIni, const IniFile& localIni) { const IniFile* inis[2] = {&globalIni, &localIni}; @@ -64,7 +66,7 @@ void LoadPatchSection(const std::string& section, std::vector& patches, I // Take care of the previous code if (!currentPatch.name.empty()) { - patches.push_back(currentPatch); + patches->push_back(currentPatch); } currentPatch.entries.clear(); @@ -110,19 +112,56 @@ void LoadPatchSection(const std::string& section, std::vector& patches, I if (!currentPatch.name.empty() && !currentPatch.entries.empty()) { - patches.push_back(currentPatch); + patches->push_back(currentPatch); } - ReadEnabledAndDisabled(*ini, section, &patches); + ReadEnabledAndDisabled(*ini, section, patches); if (ini == &globalIni) { - for (Patch& patch : patches) + for (Patch& patch : *patches) patch.default_enabled = patch.enabled; } } } +void SavePatchSection(IniFile* local_ini, const std::vector& patches) +{ + std::vector lines; + std::vector lines_enabled; + std::vector lines_disabled; + + for (const auto& patch : patches) + { + if (patch.enabled != patch.default_enabled) + (patch.enabled ? lines_enabled : lines_disabled).emplace_back('$' + patch.name); + + if (!patch.user_defined) + continue; + + lines.emplace_back('$' + patch.name); + + for (const auto& entry : patch.entries) + { + 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)); + } + } + } + + local_ini->SetLines("OnFrame_Enabled", lines_enabled); + local_ini->SetLines("OnFrame_Disabled", lines_disabled); + local_ini->SetLines("OnFrame", lines); +} + static void LoadSpeedhacks(const std::string& section, IniFile& ini) { std::vector keys; @@ -161,7 +200,7 @@ void LoadPatches() IniFile globalIni = SConfig::GetInstance().LoadDefaultGameIni(); IniFile localIni = SConfig::GetInstance().LoadLocalGameIni(); - LoadPatchSection("OnFrame", s_on_frame, globalIni, localIni); + LoadPatchSection("OnFrame", &s_on_frame, globalIni, localIni); // Check if I'm syncing Codes if (Config::Get(Config::SESSION_CODE_SYNC_OVERRIDE)) diff --git a/Source/Core/Core/PatchEngine.h b/Source/Core/Core/PatchEngine.h index 2c6c60d21d..f3c4d3d6ac 100644 --- a/Source/Core/Core/PatchEngine.h +++ b/Source/Core/Core/PatchEngine.h @@ -42,8 +42,9 @@ struct Patch const char* PatchTypeAsString(PatchType type); int GetSpeedhackCycles(const u32 addr); -void LoadPatchSection(const std::string& section, std::vector& patches, IniFile& globalIni, - IniFile& localIni); +void LoadPatchSection(const std::string& section, std::vector* patches, + const IniFile& globalIni, const IniFile& localIni); +void SavePatchSection(IniFile* local_ini, const std::vector& patches); void LoadPatches(); bool ApplyFramePatches(); void Shutdown(); diff --git a/Source/Core/DolphinQt/Config/PatchesWidget.cpp b/Source/Core/DolphinQt/Config/PatchesWidget.cpp index fd16714d94..2c08d932c0 100644 --- a/Source/Core/DolphinQt/Config/PatchesWidget.cpp +++ b/Source/Core/DolphinQt/Config/PatchesWidget.cpp @@ -7,8 +7,6 @@ #include #include -#include - #include "Common/FileUtil.h" #include "Common/IniFile.h" #include "Common/StringUtil.h" @@ -28,7 +26,7 @@ PatchesWidget::PatchesWidget(const UICommon::GameFile& game) IniFile game_ini_default = SConfig::GetInstance().LoadDefaultGameIni(m_game_id, m_game_revision); - PatchEngine::LoadPatchSection("OnFrame", m_patches, game_ini_default, game_ini_local); + PatchEngine::LoadPatchSection("OnFrame", &m_patches, game_ini_default, game_ini_local); CreateWidgets(); ConnectWidgets(); @@ -128,44 +126,12 @@ void PatchesWidget::OnRemove() void PatchesWidget::SavePatches() { - std::vector lines; - std::vector lines_enabled; - std::vector lines_disabled; - - for (const auto& patch : m_patches) - { - if (patch.enabled != patch.default_enabled) - (patch.enabled ? lines_enabled : lines_disabled).emplace_back('$' + patch.name); - - if (!patch.user_defined) - continue; - - lines.emplace_back('$' + patch.name); - - for (const auto& entry : patch.entries) - { - 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)); - } - } - } + const std::string ini_path = File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"; IniFile game_ini_local; - game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"); - - game_ini_local.SetLines("OnFrame_Enabled", lines_enabled); - game_ini_local.SetLines("OnFrame_Disabled", lines_disabled); - game_ini_local.SetLines("OnFrame", lines); - - game_ini_local.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"); + game_ini_local.Load(ini_path); + PatchEngine::SavePatchSection(&game_ini_local, m_patches); + game_ini_local.Save(ini_path); } void PatchesWidget::Update() From b90008aadb116d1d35bf13cf866dc66292b21273 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 5 Aug 2021 19:05:12 +0200 Subject: [PATCH 2/2] Split out code for serializing/deserializing cheat lines --- Source/Core/Core/ActionReplay.cpp | 76 ++++++++------- Source/Core/Core/ActionReplay.h | 7 ++ Source/Core/Core/GeckoCodeConfig.cpp | 26 ++++- Source/Core/Core/GeckoCodeConfig.h | 4 + Source/Core/Core/PatchEngine.cpp | 96 ++++++++++--------- Source/Core/Core/PatchEngine.h | 5 + .../Core/DolphinQt/Config/CheatCodeEditor.cpp | 84 ++++------------ .../Core/DolphinQt/Config/GeckoCodeWidget.cpp | 6 +- 8 files changed, 149 insertions(+), 155 deletions(-) diff --git a/Source/Core/Core/ActionReplay.cpp b/Source/Core/Core/ActionReplay.cpp index 260f3fe232..a3ab233fa3 100644 --- a/Source/Core/Core/ActionReplay.cpp +++ b/Source/Core/Core/ActionReplay.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -215,42 +216,14 @@ std::vector LoadCodes(const IniFile& global_ini, const IniFile& local_in } else { - std::vector pieces = SplitString(line, ' '); + const auto parse_result = DeserializeLine(line); - // Check if the AR code is decrypted - if (pieces.size() == 2 && pieces[0].size() == 8 && pieces[1].size() == 8) - { - AREntry op; - bool success_addr = TryParse(pieces[0], &op.cmd_addr, 16); - bool success_val = TryParse(pieces[1], &op.value, 16); - - if (success_addr && success_val) - { - current_code.ops.push_back(op); - } - else - { - PanicAlertFmtT("Action Replay Error: invalid AR code line: {0}", line); - - if (!success_addr) - PanicAlertFmtT("The address is invalid"); - - if (!success_val) - PanicAlertFmtT("The value is invalid"); - } - } + if (std::holds_alternative(parse_result)) + current_code.ops.push_back(std::get(parse_result)); + else if (std::holds_alternative(parse_result)) + encrypted_lines.emplace_back(std::get(parse_result)); else - { - pieces = SplitString(line, '-'); - if (pieces.size() == 3 && pieces[0].size() == 4 && pieces[1].size() == 4 && - pieces[2].size() == 5) - { - // Encrypted AR code - // Decryption is done in "blocks", so we must push blocks into a vector, - // then send to decrypt when a new block is encountered, or if it's the last block. - encrypted_lines.emplace_back(pieces[0] + pieces[1] + pieces[2]); - } - } + PanicAlertFmtT("Action Replay Error: invalid AR code line: {0}", line); } } @@ -293,7 +266,7 @@ void SaveCodes(IniFile* local_ini, const std::vector& codes) lines.emplace_back('$' + code.name); for (const ActionReplay::AREntry& op : code.ops) { - lines.emplace_back(fmt::format("{:08X} {:08X}", op.cmd_addr, op.value)); + lines.emplace_back(SerializeLine(op)); } } } @@ -303,6 +276,39 @@ void SaveCodes(IniFile* local_ini, const std::vector& codes) local_ini->SetLines("ActionReplay", lines); } +std::variant DeserializeLine(const std::string& line) +{ + std::vector pieces = SplitString(line, ' '); + + // Decrypted AR code + if (pieces.size() == 2 && pieces[0].size() == 8 && pieces[1].size() == 8) + { + AREntry op; + bool success_addr = TryParse(pieces[0], &op.cmd_addr, 16); + bool success_val = TryParse(pieces[1], &op.value, 16); + + if (success_addr && success_val) + return op; + } + + // Encrypted AR code + pieces = SplitString(line, '-'); + if (pieces.size() == 3 && pieces[0].size() == 4 && pieces[1].size() == 4 && pieces[2].size() == 5) + { + // Decryption is done in "blocks", so we can't decrypt right away. Instead we push blocks into + // a vector, then send to decrypt when a new block is encountered, or if it's the last block. + return pieces[0] + pieces[1] + pieces[2]; + } + + // Parsing failed + return std::monostate{}; +} + +std::string SerializeLine(const AREntry& op) +{ + return fmt::format("{:08X} {:08X}", op.cmd_addr, op.value); +} + static void VLogInfo(std::string_view format, fmt::format_args args) { if (s_disable_logging) diff --git a/Source/Core/Core/ActionReplay.h b/Source/Core/Core/ActionReplay.h index 41c1d591df..96c4127d10 100644 --- a/Source/Core/Core/ActionReplay.h +++ b/Source/Core/Core/ActionReplay.h @@ -4,7 +4,10 @@ #pragma once #include +#include +#include #include + #include "Common/CommonTypes.h" class IniFile; @@ -44,6 +47,10 @@ void LoadAndApplyCodes(const IniFile& global_ini, const IniFile& local_ini); std::vector LoadCodes(const IniFile& global_ini, const IniFile& local_ini); void SaveCodes(IniFile* local_ini, const std::vector& codes); +using EncryptedLine = std::string; +std::variant DeserializeLine(const std::string& line); +std::string SerializeLine(const AREntry& op); + void EnableSelfLogging(bool enable); std::vector GetSelfLog(); void ClearSelfLog(); diff --git a/Source/Core/Core/GeckoCodeConfig.cpp b/Source/Core/Core/GeckoCodeConfig.cpp index 27b7c5f507..fd5d416f2e 100644 --- a/Source/Core/Core/GeckoCodeConfig.cpp +++ b/Source/Core/Core/GeckoCodeConfig.cpp @@ -4,6 +4,7 @@ #include "Core/GeckoCodeConfig.h" #include +#include #include #include #include @@ -176,8 +177,10 @@ std::vector LoadCodes(const IniFile& globalIni, const IniFile& localI { GeckoCode::Code new_code; // TODO: support options - new_code.original_line = line; - ss >> std::hex >> new_code.address >> new_code.data; + if (std::optional code = DeserializeLine(line)) + new_code = *code; + else + new_code.original_line = line; gcode.codes.push_back(new_code); } break; @@ -251,4 +254,23 @@ void SaveCodes(IniFile& inifile, const std::vector& gcodes) inifile.SetLines("Gecko_Enabled", enabled_lines); inifile.SetLines("Gecko_Disabled", disabled_lines); } + +std::optional DeserializeLine(const std::string& line) +{ + std::vector items = SplitString(line, ' '); + + GeckoCode::Code code; + code.original_line = line; + + if (items.size() < 2) + return std::nullopt; + + if (!TryParse(items[0], &code.address, 16)) + return std::nullopt; + if (!TryParse(items[1], &code.data, 16)) + return std::nullopt; + + return code; +} + } // namespace Gecko diff --git a/Source/Core/Core/GeckoCodeConfig.h b/Source/Core/Core/GeckoCodeConfig.h index a90b91dd70..1b43539c0c 100644 --- a/Source/Core/Core/GeckoCodeConfig.h +++ b/Source/Core/Core/GeckoCodeConfig.h @@ -3,8 +3,10 @@ #pragma once +#include #include #include + #include "Core/GeckoCode.h" class IniFile; @@ -14,4 +16,6 @@ namespace Gecko std::vector LoadCodes(const IniFile& globalIni, const IniFile& localIni); std::vector DownloadCodes(std::string gametdb_id, bool* succeeded); void SaveCodes(IniFile& inifile, const std::vector& gcodes); + +std::optional DeserializeLine(const std::string& line); } // namespace Gecko diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp index afd20df61c..f5fe19b7ef 100644 --- a/Source/Core/Core/PatchEngine.cpp +++ b/Source/Core/Core/PatchEngine.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -45,6 +46,52 @@ const char* PatchTypeAsString(PatchType type) return s_patch_type_strings.at(static_cast(type)); } +std::optional DeserializeLine(std::string line) +{ + std::string::size_type loc = line.find('='); + if (loc != std::string::npos) + line[loc] = ':'; + + const std::vector items = SplitString(line, ':'); + PatchEntry entry; + + if (items.size() < 3) + return std::nullopt; + + if (!TryParse(items[0], &entry.address)) + return std::nullopt; + if (!TryParse(items[2], &entry.value)) + return std::nullopt; + + if (items.size() >= 4) + { + if (!TryParse(items[3], &entry.comparand)) + return std::nullopt; + entry.conditional = true; + } + + const auto iter = std::find(s_patch_type_strings.begin(), s_patch_type_strings.end(), items[1]); + if (iter == s_patch_type_strings.end()) + return std::nullopt; + entry.type = static_cast(std::distance(s_patch_type_strings.begin(), iter)); + + return entry; +} + +std::string SerializeLine(const PatchEntry& entry) +{ + if (entry.conditional) + { + return fmt::format("0x{:08X}:{}:0x{:08X}:0x{:08X}", entry.address, + PatchEngine::PatchTypeAsString(entry.type), entry.value, entry.comparand); + } + else + { + return fmt::format("0x{:08X}:{}:0x{:08X}", entry.address, + PatchEngine::PatchTypeAsString(entry.type), entry.value); + } +} + void LoadPatchSection(const std::string& section, std::vector* patches, const IniFile& globalIni, const IniFile& localIni) { @@ -76,37 +123,8 @@ void LoadPatchSection(const std::string& section, std::vector* patches, } else { - std::string::size_type loc = line.find('='); - - if (loc != std::string::npos) - { - line[loc] = ':'; - } - - const std::vector items = SplitString(line, ':'); - - if (items.size() >= 3) - { - PatchEntry pE; - 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]); - pE.type = PatchType(std::distance(s_patch_type_strings.begin(), iter)); - - success &= (pE.type != (PatchType)3); - if (success) - { - currentPatch.entries.push_back(pE); - } - } + if (std::optional entry = DeserializeLine(line)) + currentPatch.entries.push_back(*entry); } } @@ -141,20 +159,8 @@ void SavePatchSection(IniFile* local_ini, const std::vector& patches) lines.emplace_back('$' + patch.name); - for (const auto& entry : patch.entries) - { - 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)); - } - } + for (const PatchEntry& entry : patch.entries) + lines.emplace_back(SerializeLine(entry)); } local_ini->SetLines("OnFrame_Enabled", lines_enabled); diff --git a/Source/Core/Core/PatchEngine.h b/Source/Core/Core/PatchEngine.h index f3c4d3d6ac..9c0a7ed9ee 100644 --- a/Source/Core/Core/PatchEngine.h +++ b/Source/Core/Core/PatchEngine.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -42,10 +43,14 @@ struct Patch const char* PatchTypeAsString(PatchType type); int GetSpeedhackCycles(const u32 addr); + +std::optional DeserializeLine(std::string line); +std::string SerializeLine(const PatchEntry& entry); void LoadPatchSection(const std::string& section, std::vector* patches, const IniFile& globalIni, const IniFile& localIni); void SavePatchSection(IniFile* local_ini, const std::vector& patches); void LoadPatches(); + bool ApplyFramePatches(); void Shutdown(); void Reload(); diff --git a/Source/Core/DolphinQt/Config/CheatCodeEditor.cpp b/Source/Core/DolphinQt/Config/CheatCodeEditor.cpp index 65a5cc78c9..b8a62c36b6 100644 --- a/Source/Core/DolphinQt/Config/CheatCodeEditor.cpp +++ b/Source/Core/DolphinQt/Config/CheatCodeEditor.cpp @@ -32,16 +32,10 @@ void CheatCodeEditor::SetARCode(ActionReplay::ARCode* code) { m_name_edit->setText(QString::fromStdString(code->name)); - QString s; + m_code_edit->clear(); for (ActionReplay::AREntry& e : code->ops) - { - s += QStringLiteral("%1 %2\n") - .arg(e.cmd_addr, 8, 16, QLatin1Char('0')) - .arg(e.value, 8, 16, QLatin1Char('0')); - } - - m_code_edit->setText(s); + m_code_edit->append(QString::fromStdString(ActionReplay::SerializeLine(e))); m_creator_label->setHidden(true); m_creator_edit->setHidden(true); @@ -57,14 +51,10 @@ void CheatCodeEditor::SetGeckoCode(Gecko::GeckoCode* code) m_name_edit->setText(QString::fromStdString(code->name)); m_creator_edit->setText(QString::fromStdString(code->creator)); - QString code_string; + m_code_edit->clear(); for (const auto& c : code->codes) - code_string += QStringLiteral("%1 %2\n") - .arg(c.address, 8, 16, QLatin1Char('0')) - .arg(c.data, 8, 16, QLatin1Char('0')); - - m_code_edit->setText(code_string); + m_code_edit->append(QString::fromStdString(c.original_line)); QString notes_string; for (const auto& line : code->notes) @@ -135,7 +125,6 @@ bool CheatCodeEditor::AcceptAR() if (line.isEmpty()) continue; - if (i == 0 && line[0] == u'$') { if (name.isEmpty()) @@ -144,40 +133,17 @@ bool CheatCodeEditor::AcceptAR() continue; } - QStringList values = line.split(QLatin1Char{' '}); + const auto parse_result = ActionReplay::DeserializeLine(line.toStdString()); - bool good = true; - - u32 addr = 0; - u32 value = 0; - - if (values.size() == 2) + if (std::holds_alternative(parse_result)) { - addr = values[0].toUInt(&good, 16); - - if (good) - value = values[1].toUInt(&good, 16); - - if (good) - entries.push_back(ActionReplay::AREntry(addr, value)); + entries.push_back(std::get(parse_result)); + } + else if (std::holds_alternative(parse_result)) + { + encrypted_lines.emplace_back(std::get(parse_result)); } else - { - QStringList blocks = line.split(QLatin1Char{'-'}); - - if (blocks.size() == 3 && blocks[0].size() == 4 && blocks[1].size() == 4 && - blocks[2].size() == 5) - { - encrypted_lines.emplace_back(blocks[0].toStdString() + blocks[1].toStdString() + - blocks[2].toStdString()); - } - else - { - good = false; - } - } - - if (!good) { auto result = ModalMessageBox::warning( this, tr("Parsing Error"), @@ -260,20 +226,11 @@ bool CheatCodeEditor::AcceptGecko() continue; } - QStringList values = line.split(QLatin1Char{' '}); - - bool good = values.size() == 2; - - u32 addr = 0; - u32 value = 0; - - if (good) - addr = values[0].toUInt(&good, 16); - - if (good) - value = values[1].toUInt(&good, 16); - - if (!good) + if (std::optional c = Gecko::DeserializeLine(line.toStdString())) + { + entries.push_back(*c); + } + else { auto result = ModalMessageBox::warning( this, tr("Parsing Error"), @@ -286,15 +243,6 @@ bool CheatCodeEditor::AcceptGecko() if (result == QMessageBox::Abort) return false; } - else - { - Gecko::GeckoCode::Code c; - c.address = addr; - c.data = value; - c.original_line = line.toStdString(); - - entries.push_back(c); - } } if (entries.empty()) diff --git a/Source/Core/DolphinQt/Config/GeckoCodeWidget.cpp b/Source/Core/DolphinQt/Config/GeckoCodeWidget.cpp index 4fc99a0dc4..99110d3b37 100644 --- a/Source/Core/DolphinQt/Config/GeckoCodeWidget.cpp +++ b/Source/Core/DolphinQt/Config/GeckoCodeWidget.cpp @@ -167,11 +167,7 @@ void GeckoCodeWidget::OnSelectionChanged() m_code_view->clear(); for (const auto& c : code.codes) - { - m_code_view->append(QStringLiteral("%1 %2") - .arg(c.address, 8, 16, QLatin1Char('0')) - .arg(c.data, 8, 16, QLatin1Char('0'))); - } + m_code_view->append(QString::fromStdString(c.original_line)); } void GeckoCodeWidget::OnItemChanged(QListWidgetItem* item)