From 81da9fb5a45f962796b0462c1f2a692e06ebb6dc Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 26 May 2023 02:24:01 +1000 Subject: [PATCH] Patch: Add new toggleable cheat and patch interface --- pcsx2-gsrunner/Main.cpp | 1 - pcsx2-qt/CMakeLists.txt | 8 + pcsx2-qt/QtHost.cpp | 7 +- pcsx2-qt/Settings/EmulationSettingsWidget.cpp | 21 +- pcsx2-qt/Settings/GameCheatSettingsWidget.cpp | 236 +++ pcsx2-qt/Settings/GameCheatSettingsWidget.h | 68 + pcsx2-qt/Settings/GameCheatSettingsWidget.ui | 116 ++ pcsx2-qt/Settings/GamePatchDetailsWidget.ui | 78 + pcsx2-qt/Settings/GamePatchSettingsWidget.cpp | 132 ++ pcsx2-qt/Settings/GamePatchSettingsWidget.h | 69 + pcsx2-qt/Settings/GamePatchSettingsWidget.ui | 74 + pcsx2-qt/Settings/PatchDetailsWidget.ui | 72 + pcsx2-qt/Settings/SettingsDialog.cpp | 19 +- pcsx2-qt/Settings/SettingsDialog.h | 8 +- pcsx2-qt/pcsx2-qt.vcxproj | 15 +- pcsx2-qt/pcsx2-qt.vcxproj.filters | 20 + .../resources/icons/black/svg/hammer-line.svg | 1 + .../resources/icons/black/svg/tools-line.svg | 1 + .../resources/icons/white/svg/hammer-line.svg | 1 + .../resources/icons/white/svg/tools-line.svg | 1 + pcsx2-qt/resources/resources.qrc | 4 + pcsx2/CMakeLists.txt | 1 - pcsx2/Config.h | 13 +- pcsx2/GameDatabase.cpp | 42 +- pcsx2/GameDatabase.h | 2 +- pcsx2/ImGui/FullscreenUI.cpp | 3 +- pcsx2/Patch.cpp | 1417 ++++++++++++++--- pcsx2/Patch.h | 178 +-- pcsx2/Patch_Memory.cpp | 609 ------- pcsx2/Pcsx2Config.cpp | 15 +- pcsx2/VMManager.cpp | 448 ++---- pcsx2/VMManager.h | 3 - pcsx2/pcsx2.vcxproj | 1 - pcsx2/pcsx2.vcxproj.filters | 3 - pcsx2/x86/ix86-32/iR5900-32.cpp | 2 +- tools/merge_ws_ni_patches.py | 73 + 36 files changed, 2512 insertions(+), 1250 deletions(-) create mode 100644 pcsx2-qt/Settings/GameCheatSettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/GameCheatSettingsWidget.h create mode 100644 pcsx2-qt/Settings/GameCheatSettingsWidget.ui create mode 100644 pcsx2-qt/Settings/GamePatchDetailsWidget.ui create mode 100644 pcsx2-qt/Settings/GamePatchSettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/GamePatchSettingsWidget.h create mode 100644 pcsx2-qt/Settings/GamePatchSettingsWidget.ui create mode 100644 pcsx2-qt/Settings/PatchDetailsWidget.ui create mode 100644 pcsx2-qt/resources/icons/black/svg/hammer-line.svg create mode 100644 pcsx2-qt/resources/icons/black/svg/tools-line.svg create mode 100644 pcsx2-qt/resources/icons/white/svg/hammer-line.svg create mode 100644 pcsx2-qt/resources/icons/white/svg/tools-line.svg delete mode 100644 pcsx2/Patch_Memory.cpp create mode 100644 tools/merge_ws_ni_patches.py diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp index c52897d11f..c6308324a1 100644 --- a/pcsx2-gsrunner/Main.cpp +++ b/pcsx2-gsrunner/Main.cpp @@ -371,7 +371,6 @@ SysMtgsThread& GetMTGS() // Interface Stuff ////////////////////////////////////////////////////////////////////////// -const IConsoleWriter* PatchesCon = &Console; BEGIN_HOTKEY_LIST(g_host_hotkeys) END_HOTKEY_LIST() diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index 99951fe682..86a19a381e 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -76,12 +76,19 @@ target_sources(pcsx2-qt PRIVATE Settings/FolderSettingsWidget.cpp Settings/FolderSettingsWidget.h Settings/FolderSettingsWidget.ui + Settings/GameCheatSettingsWidget.cpp + Settings/GameCheatSettingsWidget.h + Settings/GameCheatSettingsWidget.ui Settings/GameFixSettingsWidget.cpp Settings/GameFixSettingsWidget.h Settings/GameFixSettingsWidget.ui Settings/GameListSettingsWidget.cpp Settings/GameListSettingsWidget.h Settings/GameListSettingsWidget.ui + Settings/GamePatchDetailsWidget.ui + Settings/GamePatchSettingsWidget.cpp + Settings/GamePatchSettingsWidget.h + Settings/GamePatchSettingsWidget.ui Settings/GameSummaryWidget.cpp Settings/GameSummaryWidget.h Settings/GameSummaryWidget.ui @@ -116,6 +123,7 @@ target_sources(pcsx2-qt PRIVATE Settings/DEV9UiCommon.h Settings/HddCreateQt.cpp Settings/HddCreateQt.h + Settings/PatchDetailsWidget.ui Settings/SettingsDialog.cpp Settings/SettingsDialog.h Settings/SettingsDialog.ui diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 07f0914fc7..9c27c44986 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -84,7 +84,6 @@ namespace QtHost ////////////////////////////////////////////////////////////////////////// // Local variable declarations ////////////////////////////////////////////////////////////////////////// -const IConsoleWriter* PatchesCon = &Console; static std::unique_ptr s_settings_save_timer; static std::unique_ptr s_base_settings_interface; static bool s_batch_mode = false; @@ -671,7 +670,11 @@ void EmuThread::reloadPatches() if (!VMManager::HasValidVM()) return; - VMManager::ReloadPatches(true, true); + Patch::ReloadPatches(true, false, true); + + // Might change widescreen mode. + if (Patch::ReloadPatchAffectingOptions()) + applySettings(); } void EmuThread::reloadInputSources() diff --git a/pcsx2-qt/Settings/EmulationSettingsWidget.cpp b/pcsx2-qt/Settings/EmulationSettingsWidget.cpp index 4656161ac1..3318ebe210 100644 --- a/pcsx2-qt/Settings/EmulationSettingsWidget.cpp +++ b/pcsx2-qt/Settings/EmulationSettingsWidget.cpp @@ -64,9 +64,29 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsDialog* dialog, QWidget .arg(m_ui.eeCycleRate->itemText( std::clamp(Host::GetBaseIntSettingValue("EmuCore/Speedhacks", "EECycleRate", DEFAULT_EE_CYCLE_RATE) - MINIMUM_EE_CYCLE_RATE, 0, MAXIMUM_EE_CYCLE_RATE - MINIMUM_EE_CYCLE_RATE)))); + + // Disable cheats, use the cheats panel instead (move fastcvd up in its spot). + const int count = m_ui.systemSettingsLayout->count(); + for (int i = 0; i < count; i++) + { + QLayoutItem* item = m_ui.systemSettingsLayout->itemAt(i); + if (item && item->widget() == m_ui.cheats) + { + int row, col, rowSpan, colSpan; + m_ui.systemSettingsLayout->getItemPosition(i, &row, &col, &rowSpan, &colSpan); + delete m_ui.systemSettingsLayout->takeAt(i); + m_ui.systemSettingsLayout->removeWidget(m_ui.fastCDVD); + m_ui.systemSettingsLayout->addWidget(m_ui.fastCDVD, row, col); + delete m_ui.cheats; + m_ui.cheats = nullptr; + break; + } + } } else { + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.cheats, "EmuCore", "EnableCheats", false); + // Allow for FastCDVD for per-game settings only m_ui.systemSettingsLayout->removeWidget(m_ui.fastCDVD); m_ui.fastCDVD->deleteLater(); @@ -84,7 +104,6 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsDialog* dialog, QWidget m_dialog->setIntSettingValue("EmuCore/Speedhacks", "EECycleRate", value); }); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.cheats, "EmuCore", "EnableCheats", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hostFilesystem, "EmuCore", "HostFs", false); dialog->registerWidgetHelp(m_ui.normalSpeed, tr("Normal Speed"), "100%", diff --git a/pcsx2-qt/Settings/GameCheatSettingsWidget.cpp b/pcsx2-qt/Settings/GameCheatSettingsWidget.cpp new file mode 100644 index 0000000000..aea87252ca --- /dev/null +++ b/pcsx2-qt/Settings/GameCheatSettingsWidget.cpp @@ -0,0 +1,236 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2023 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "MainWindow.h" +#include "QtHost.h" +#include "QtUtils.h" +#include "SettingWidgetBinder.h" +#include "Settings/GameCheatSettingsWidget.h" +#include "Settings/SettingsDialog.h" + +#include "pcsx2/GameList.h" +#include "pcsx2/Patch.h" + +#include "common/HeterogeneousContainers.h" + +GameCheatSettingsWidget::GameCheatSettingsWidget(const GameList::Entry* entry, SettingsDialog* dialog, QWidget* parent) + : m_dialog(dialog) + , m_serial(entry->serial) + , m_crc(entry->crc) +{ + m_ui.setupUi(this); + QtUtils::ResizeColumnsForTreeView(m_ui.cheatList, {300, 100, -1}); + + reloadList(); + + SettingsInterface* sif = m_dialog->getSettingsInterface(); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableCheats, "EmuCore", "EnableCheats", false); + updateListEnabled(); + + connect(m_ui.enableCheats, &QCheckBox::stateChanged, this, &GameCheatSettingsWidget::updateListEnabled); + connect(m_ui.cheatList, &QTreeWidget::itemDoubleClicked, this, &GameCheatSettingsWidget::onCheatListItemDoubleClicked); + connect(m_ui.cheatList, &QTreeWidget::itemChanged, this, &GameCheatSettingsWidget::onCheatListItemChanged); + connect(m_ui.reloadCheats, &QPushButton::clicked, this, &GameCheatSettingsWidget::reloadList); + connect(m_ui.enableAll, &QPushButton::clicked, this, [this]() { setStateForAll(true); }); + connect(m_ui.disableAll, &QPushButton::clicked, this, [this]() { setStateForAll(false); }); +} + +GameCheatSettingsWidget::~GameCheatSettingsWidget() = default; + +void GameCheatSettingsWidget::onCheatListItemDoubleClicked(QTreeWidgetItem* item, int column) +{ + QVariant data = item->data(0, Qt::UserRole); + if (!data.isValid()) + return; + + std::string cheat_name = data.toString().toStdString(); + const bool new_state = !(item->checkState(0) == Qt::Checked); + item->setCheckState(0, new_state ? Qt::Checked : Qt::Unchecked); + setCheatEnabled(std::move(cheat_name), new_state, true); +} + +void GameCheatSettingsWidget::onCheatListItemChanged(QTreeWidgetItem* item, int column) +{ + QVariant data = item->data(0, Qt::UserRole); + if (!data.isValid()) + return; + + std::string cheat_name = data.toString().toStdString(); + const bool current_enabled = + (std::find(m_enabled_patches.begin(), m_enabled_patches.end(), cheat_name) != m_enabled_patches.end()); + const bool current_checked = (item->checkState(0) == Qt::Checked); + if (current_enabled == current_checked) + return; + + setCheatEnabled(std::move(cheat_name), current_checked, true); +} + +void GameCheatSettingsWidget::onReloadClicked() +{ + reloadList(); + + // reload it on the emu thread too, so it picks up any changes + g_emu_thread->reloadPatches(); +} + +void GameCheatSettingsWidget::updateListEnabled() +{ + const bool cheats_enabled = m_dialog->getEffectiveBoolValue("EmuCore", "EnableCheats", false); + m_ui.cheatList->setEnabled(cheats_enabled); + m_ui.enableAll->setEnabled(cheats_enabled); + m_ui.disableAll->setEnabled(cheats_enabled); + m_ui.reloadCheats->setEnabled(cheats_enabled); +} + +void GameCheatSettingsWidget::setCheatEnabled(std::string name, bool enabled, bool save_and_reload_settings) +{ + SettingsInterface* si = m_dialog->getSettingsInterface(); + auto it = std::find(m_enabled_patches.begin(), m_enabled_patches.end(), name); + + if (enabled) + { + si->AddToStringList(Patch::CHEATS_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY, name.c_str()); + if (it == m_enabled_patches.end()) + m_enabled_patches.push_back(std::move(name)); + } + else + { + si->RemoveFromStringList(Patch::CHEATS_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY, name.c_str()); + if (it != m_enabled_patches.end()) + m_enabled_patches.erase(it); + } + + if (save_and_reload_settings) + { + si->Save(); + g_emu_thread->reloadGameSettings(); + } +} + +void GameCheatSettingsWidget::setStateForAll(bool enabled) +{ + QSignalBlocker sb(m_ui.cheatList); + setStateRecursively(nullptr, enabled); + m_dialog->getSettingsInterface()->Save(); + g_emu_thread->reloadGameSettings(); +} + +void GameCheatSettingsWidget::setStateRecursively(QTreeWidgetItem* parent, bool enabled) +{ + const int count = parent ? parent->childCount() : m_ui.cheatList->topLevelItemCount(); + for (int i = 0; i < count; i++) + { + QTreeWidgetItem* item = parent ? parent->child(i) : m_ui.cheatList->topLevelItem(i); + QVariant data = item->data(0, Qt::UserRole); + if (data.isValid()) + { + if ((item->checkState(0) == Qt::Checked) != enabled) + { + item->setCheckState(0, enabled ? Qt::Checked : Qt::Unchecked); + setCheatEnabled(data.toString().toStdString(), enabled, false); + } + } + else + { + setStateRecursively(item, enabled); + } + } +} + +void GameCheatSettingsWidget::reloadList() +{ + u32 num_unlabelled_codes = 0; + m_patches = Patch::GetPatchInfo(m_serial, m_crc, true, &num_unlabelled_codes); + m_enabled_patches = + m_dialog->getSettingsInterface()->GetStringList(Patch::CHEATS_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY); + + m_parent_map.clear(); + while (m_ui.cheatList->topLevelItemCount() > 0) + delete m_ui.cheatList->takeTopLevelItem(0); + + for (const Patch::PatchInfo& pi : m_patches) + { + const bool enabled = + (std::find(m_enabled_patches.begin(), m_enabled_patches.end(), pi.name) != m_enabled_patches.end()); + + const std::string_view name_part = pi.GetNamePart(); + const std::string_view parent_part = pi.GetNameParentPart(); + + QTreeWidgetItem* parent = getTreeWidgetParent(parent_part); + QTreeWidgetItem* item = new QTreeWidgetItem(); + populateTreeWidgetItem(item, pi, enabled); + if (parent) + parent->addChild(item); + else + m_ui.cheatList->addTopLevelItem(item); + } + + // Hide root indicator when there's no groups, frees up some whitespace. + m_ui.cheatList->setRootIsDecorated(!m_parent_map.empty()); + + if (num_unlabelled_codes > 0) + { + QTreeWidgetItem* item = new QTreeWidgetItem(); + item->setText(0, tr("%1 unlabelled patch codes will automatically activate.").arg(num_unlabelled_codes)); + m_ui.cheatList->addTopLevelItem(item); + } +} + +QTreeWidgetItem* GameCheatSettingsWidget::getTreeWidgetParent(const std::string_view& parent) +{ + if (parent.empty()) + return nullptr; + + auto it = UnorderedStringMapFind(m_parent_map, parent); + if (it != m_parent_map.end()) + return it->second; + + std::string_view this_part = parent; + QTreeWidgetItem* parent_to_this = nullptr; + const std::string_view::size_type pos = parent.rfind('\\'); + if (pos != std::string::npos && pos != (parent.size() - 1)) + { + // go up the chain until we find the real parent, then back down + parent_to_this = getTreeWidgetParent(parent.substr(0, pos)); + this_part = parent.substr(pos + 1); + } + + QTreeWidgetItem* item = new QTreeWidgetItem(); + item->setText(0, QString::fromUtf8(this_part.data(), this_part.length())); + + if (parent_to_this) + parent_to_this->addChild(item); + else + m_ui.cheatList->addTopLevelItem(item); + + // Must be called after adding. + item->setExpanded(true); + m_parent_map.emplace(parent, item); + return item; +} + +void GameCheatSettingsWidget::populateTreeWidgetItem(QTreeWidgetItem* item, const Patch::PatchInfo& pi, bool enabled) +{ + const std::string_view name_part = pi.GetNamePart(); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemNeverHasChildren); + item->setCheckState(0, enabled ? Qt::Checked : Qt::Unchecked); + item->setData(0, Qt::UserRole, QString::fromStdString(pi.name)); + if (!name_part.empty()) + item->setText(0, QString::fromUtf8(name_part.data(), name_part.length())); + item->setText(1, QString::fromStdString(pi.author)); + item->setText(2, QString::fromStdString(pi.description)); +} diff --git a/pcsx2-qt/Settings/GameCheatSettingsWidget.h b/pcsx2-qt/Settings/GameCheatSettingsWidget.h new file mode 100644 index 0000000000..a4005f36b5 --- /dev/null +++ b/pcsx2-qt/Settings/GameCheatSettingsWidget.h @@ -0,0 +1,68 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2023 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include + +#include "ui_GameCheatSettingsWidget.h" + +#include "pcsx2/Patch.h" + +#include "common/HeterogeneousContainers.h" + +#include +#include +#include + +namespace GameList +{ + struct Entry; +} + +class SettingsDialog; + +class GameCheatSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + GameCheatSettingsWidget(const GameList::Entry* entry, SettingsDialog* dialog, QWidget* parent); + ~GameCheatSettingsWidget(); + +private Q_SLOTS: + void onCheatListItemDoubleClicked(QTreeWidgetItem* item, int column); + void onCheatListItemChanged(QTreeWidgetItem* item, int column); + void onReloadClicked(); + void updateListEnabled(); + +private: + QTreeWidgetItem* getTreeWidgetParent(const std::string_view& parent); + void populateTreeWidgetItem(QTreeWidgetItem* item, const Patch::PatchInfo& pi, bool enabled); + void setCheatEnabled(std::string name, bool enabled, bool save_and_reload_settings); + void setStateForAll(bool enabled); + void setStateRecursively(QTreeWidgetItem* parent, bool enabled); + void reloadList(); + + Ui::GameCheatSettingsWidget m_ui; + SettingsDialog* m_dialog; + + std::string m_serial; + u32 m_crc; + + UnorderedStringMap m_parent_map; + std::vector m_patches; + std::vector m_enabled_patches; +}; diff --git a/pcsx2-qt/Settings/GameCheatSettingsWidget.ui b/pcsx2-qt/Settings/GameCheatSettingsWidget.ui new file mode 100644 index 0000000000..b3b9ea8a65 --- /dev/null +++ b/pcsx2-qt/Settings/GameCheatSettingsWidget.ui @@ -0,0 +1,116 @@ + + + GameCheatSettingsWidget + + + + 0 + 0 + 697 + 361 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Activating cheats can cause unpredictable behavior, crashing, soft-locks, or broken saved games. Use cheats at your own risk, the PCSX2 team will provide no support for users who have enabled cheats. + + + true + + + + + + + Enable Cheats + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectItems + + + + Name + + + + + Author + + + + + Description + + + + + + + + + + Enable All + + + + + + + Disable All + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reload Cheats + + + + + + + + + + diff --git a/pcsx2-qt/Settings/GamePatchDetailsWidget.ui b/pcsx2-qt/Settings/GamePatchDetailsWidget.ui new file mode 100644 index 0000000000..21f6ba5482 --- /dev/null +++ b/pcsx2-qt/Settings/GamePatchDetailsWidget.ui @@ -0,0 +1,78 @@ + + + GamePatchDetailsWidget + + + + 0 + 0 + 541 + 112 + + + + + 0 + 0 + + + + Form + + + + + + + + + 12 + true + + + + Patch Title + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Enabled + + + + + + + + + + + <html><head/><body><p><span style=" font-weight:700;">Author: </span>Patch Author</p><p>Description would go here</p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + diff --git a/pcsx2-qt/Settings/GamePatchSettingsWidget.cpp b/pcsx2-qt/Settings/GamePatchSettingsWidget.cpp new file mode 100644 index 0000000000..5c54620257 --- /dev/null +++ b/pcsx2-qt/Settings/GamePatchSettingsWidget.cpp @@ -0,0 +1,132 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2023 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "MainWindow.h" +#include "QtHost.h" +#include "QtUtils.h" +#include "Settings/GamePatchSettingsWidget.h" +#include "Settings/SettingsDialog.h" + +#include "pcsx2/GameList.h" +#include "pcsx2/Patch.h" + +#include + +GamePatchDetailsWidget::GamePatchDetailsWidget(std::string name, const std::string& author, + const std::string& description, bool enabled, SettingsDialog* dialog, QWidget* parent) + : QWidget(parent) + , m_dialog(dialog) + , m_name(name) +{ + m_ui.setupUi(this); + + m_ui.name->setText(QString::fromStdString(name)); + m_ui.description->setText( + tr("Author: %1
%2") + .arg(author.empty() ? tr("Unknown") : QString::fromStdString(author)) + .arg(description.empty() ? tr("No description provided.") : QString::fromStdString(description))); + + pxAssert(dialog->getSettingsInterface()); + m_ui.enabled->setChecked(enabled); + connect(m_ui.enabled, &QCheckBox::stateChanged, this, &GamePatchDetailsWidget::onEnabledStateChanged); +} + +GamePatchDetailsWidget::~GamePatchDetailsWidget() = default; + +void GamePatchDetailsWidget::onEnabledStateChanged(int state) +{ + SettingsInterface* si = m_dialog->getSettingsInterface(); + if (state == Qt::Checked) + si->AddToStringList("Patches", "Enable", m_name.c_str()); + else + si->RemoveFromStringList("Patches", "Enable", m_name.c_str()); + + si->Save(); + g_emu_thread->reloadGameSettings(); +} + +GamePatchSettingsWidget::GamePatchSettingsWidget(const GameList::Entry* entry, SettingsDialog* dialog, QWidget* parent) + : m_dialog(dialog) + , m_serial(entry->serial) + , m_crc(entry->crc) +{ + m_ui.setupUi(this); + m_ui.scrollArea->setFrameShape(QFrame::WinPanel); + m_ui.scrollArea->setFrameShadow(QFrame::Sunken); + + connect(m_ui.reload, &QPushButton::clicked, this, &GamePatchSettingsWidget::onReloadClicked); + + reloadList(); +} + +GamePatchSettingsWidget::~GamePatchSettingsWidget() = default; + +void GamePatchSettingsWidget::onReloadClicked() +{ + reloadList(); + + // reload it on the emu thread too, so it picks up any changes + g_emu_thread->reloadPatches(); +} + +void GamePatchSettingsWidget::reloadList() +{ + // Patches shouldn't have any unlabelled patch groups, because they're new. + std::vector patches = Patch::GetPatchInfo(m_serial, m_crc, false, nullptr); + std::vector enabled_list = + m_dialog->getSettingsInterface()->GetStringList(Patch::PATCHES_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY); + + delete m_ui.scrollArea->takeWidget(); + + QWidget* container = new QWidget(m_ui.scrollArea); + QVBoxLayout* layout = new QVBoxLayout(container); + layout->setContentsMargins(0, 0, 0, 0); + + if (!patches.empty()) + { + bool first = true; + + for (Patch::PatchInfo& pi : patches) + { + if (!first) + { + QFrame* frame = new QFrame(container); + frame->setFrameShape(QFrame::HLine); + frame->setFrameShadow(QFrame::Sunken); + layout->addWidget(frame); + } + else + { + first = false; + } + + const bool enabled = (std::find(enabled_list.begin(), enabled_list.end(), pi.name) != enabled_list.end()); + GamePatchDetailsWidget* it = + new GamePatchDetailsWidget(std::move(pi.name), pi.author, pi.description, enabled, m_dialog, container); + layout->addWidget(it); + } + } + else + { + QLabel* label = new QLabel(tr("There are no patches available for this game."), container); + layout->addWidget(label); + } + + layout->addStretch(1); + + m_ui.scrollArea->setWidget(container); +} diff --git a/pcsx2-qt/Settings/GamePatchSettingsWidget.h b/pcsx2-qt/Settings/GamePatchSettingsWidget.h new file mode 100644 index 0000000000..83fa280c2e --- /dev/null +++ b/pcsx2-qt/Settings/GamePatchSettingsWidget.h @@ -0,0 +1,69 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2023 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include + +#include "ui_GamePatchDetailsWidget.h" +#include "ui_GamePatchSettingsWidget.h" + +#include "pcsx2/Patch.h" + +namespace GameList +{ + struct Entry; +} + +class SettingsDialog; + +class GamePatchDetailsWidget : public QWidget +{ + Q_OBJECT + +public: + GamePatchDetailsWidget(std::string name, const std::string& author, const std::string& description, bool enabled, + SettingsDialog* dialog, QWidget* parent); + ~GamePatchDetailsWidget(); + +private Q_SLOTS: + void onEnabledStateChanged(int state); + +private: + Ui::GamePatchDetailsWidget m_ui; + SettingsDialog* m_dialog; + std::string m_name; +}; + +class GamePatchSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + GamePatchSettingsWidget(const GameList::Entry* entry, SettingsDialog* dialog, QWidget* parent); + ~GamePatchSettingsWidget(); + +private Q_SLOTS: + void onReloadClicked(); + +private: + void reloadList(); + + Ui::GamePatchSettingsWidget m_ui; + SettingsDialog* m_dialog; + + std::string m_serial; + u32 m_crc; +}; diff --git a/pcsx2-qt/Settings/GamePatchSettingsWidget.ui b/pcsx2-qt/Settings/GamePatchSettingsWidget.ui new file mode 100644 index 0000000000..1f78834135 --- /dev/null +++ b/pcsx2-qt/Settings/GamePatchSettingsWidget.ui @@ -0,0 +1,74 @@ + + + GamePatchSettingsWidget + + + + 0 + 0 + 766 + 392 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Activating game patches can cause unpredictable behavior, crashing, soft-locks, or broken saved games. Use patches at your own risk, the PCSX2 team will provide no support for users who have enabled game patches. + + + true + + + + + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reload Patches + + + + + + + + + + diff --git a/pcsx2-qt/Settings/PatchDetailsWidget.ui b/pcsx2-qt/Settings/PatchDetailsWidget.ui new file mode 100644 index 0000000000..08ed21893b --- /dev/null +++ b/pcsx2-qt/Settings/PatchDetailsWidget.ui @@ -0,0 +1,72 @@ + + + GamePatchDetailsWidget + + + + 0 + 0 + 541 + 112 + + + + Form + + + + + + + + + 16 + true + + + + Patch Title + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Enabled + + + + + + + + + + + <html><head/><body><p><span style=" font-weight:700;">Author: </span>Patch Author</p><p>Description would go here</p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + diff --git a/pcsx2-qt/Settings/SettingsDialog.cpp b/pcsx2-qt/Settings/SettingsDialog.cpp index cd692163b6..e6a82e0ef1 100644 --- a/pcsx2-qt/Settings/SettingsDialog.cpp +++ b/pcsx2-qt/Settings/SettingsDialog.cpp @@ -12,7 +12,7 @@ * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ - + #include "PrecompiledHeader.h" #include "MainWindow.h" @@ -26,8 +26,10 @@ #include "Settings/DebugSettingsWidget.h" #include "Settings/EmulationSettingsWidget.h" #include "Settings/FolderSettingsWidget.h" +#include "Settings/GameCheatSettingsWidget.h" #include "Settings/GameFixSettingsWidget.h" #include "Settings/GameListSettingsWidget.h" +#include "Settings/GamePatchSettingsWidget.h" #include "Settings/GameSummaryWidget.h" #include "Settings/GraphicsSettingsWidget.h" #include "Settings/HotkeySettingsWidget.h" @@ -106,11 +108,24 @@ void SettingsDialog::setupUi(const GameList::Entry* game) tr("Emulation Settings
These options determine the configuration of frame pacing and game " "settings.

Mouse over an option for additional information.")); + if (isPerGameSettings() && game && game->crc != 0) + { + addWidget(m_game_patch_settings_widget = new GamePatchSettingsWidget(game, this, m_ui.settingsContainer), + tr("Patches"), QStringLiteral("tools-line"), + tr("Patches
This section allows you to select optional patches to apply to the game, " + "which may provide performance, visual, or gameplay improvements.")); + addWidget(m_game_cheat_settings_widget = new GameCheatSettingsWidget(game, this, m_ui.settingsContainer), + tr("Cheats"), QStringLiteral("flask-line"), + tr("Cheats
This section allows you to select which cheats you wish to enable. You " + "cannot enable/disable cheats without labels for old-format pnach files, those will automatically " + "activate if the main cheat enable option is checked.")); + } + // Only show the game fixes for per-game settings, there's really no reason to be setting them globally. if (show_advanced_settings && isPerGameSettings()) { addWidget(m_game_fix_settings_widget = new GameFixSettingsWidget(this, m_ui.settingsContainer), tr("Game Fixes"), - QStringLiteral("close-line"), + QStringLiteral("hammer-line"), tr("Game Fixes Settings
Game Fixes can work around incorrect emulation in some titles.
However, they can " "also cause problems in games if used incorrectly.
It is best to leave them all disabled unless advised otherwise.")); } diff --git a/pcsx2-qt/Settings/SettingsDialog.h b/pcsx2-qt/Settings/SettingsDialog.h index d1a0876f44..d5986554a8 100644 --- a/pcsx2-qt/Settings/SettingsDialog.h +++ b/pcsx2-qt/Settings/SettingsDialog.h @@ -33,7 +33,9 @@ class InterfaceSettingsWidget; class GameListSettingsWidget; class EmulationSettingsWidget; class BIOSSettingsWidget; +class GameCheatSettingsWidget; class GameFixSettingsWidget; +class GamePatchSettingsWidget; class GraphicsSettingsWidget; class AudioSettingsWidget; class MemoryCardSettingsWidget; @@ -61,7 +63,9 @@ public: __fi GameListSettingsWidget* getGameListSettingsWidget() const { return m_game_list_settings; } __fi BIOSSettingsWidget* getBIOSSettingsWidget() const { return m_bios_settings; } __fi EmulationSettingsWidget* getEmulationSettingsWidget() const { return m_emulation_settings; } + __fi GameCheatSettingsWidget* getGameCheatSettingsWidget() const { return m_game_cheat_settings_widget; } __fi GameFixSettingsWidget* getGameFixSettingsWidget() const { return m_game_fix_settings_widget; } + __fi GamePatchSettingsWidget* getGamePatchSettingsWidget() const { return m_game_patch_settings_widget; } __fi GraphicsSettingsWidget* getGraphicsSettingsWidget() const { return m_graphics_settings; } __fi AudioSettingsWidget* getAudioSettingsWidget() const { return m_audio_settings; } __fi MemoryCardSettingsWidget* getMemoryCardSettingsWidget() const { return m_memory_card_settings; } @@ -107,7 +111,7 @@ protected: private: enum : u32 { - MAX_SETTINGS_WIDGETS = 12 + MAX_SETTINGS_WIDGETS = 13 }; void setupUi(const GameList::Entry* game); @@ -122,7 +126,9 @@ private: GameListSettingsWidget* m_game_list_settings = nullptr; BIOSSettingsWidget* m_bios_settings = nullptr; EmulationSettingsWidget* m_emulation_settings = nullptr; + GameCheatSettingsWidget* m_game_cheat_settings_widget = nullptr; GameFixSettingsWidget* m_game_fix_settings_widget = nullptr; + GamePatchSettingsWidget* m_game_patch_settings_widget = nullptr; GraphicsSettingsWidget* m_graphics_settings = nullptr; AudioSettingsWidget* m_audio_settings = nullptr; MemoryCardSettingsWidget* m_memory_card_settings = nullptr; diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index c68d20adfe..b47735f1be 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -48,7 +48,6 @@ Use PrecompiledHeader.h LZMA_API_STATIC;BUILD_DX=1;ENABLE_RAINTEGRATION;ENABLE_ACHIEVEMENTS;ENABLE_DISCORD_PRESENCE;ENABLE_OPENGL;ENABLE_VULKAN;SDL_BUILD;%(PreprocessorDefinitions) - true @@ -85,6 +84,8 @@ + + @@ -162,6 +163,8 @@ + + @@ -211,7 +214,9 @@ + + @@ -369,6 +374,12 @@ Document + + Document + + + Document + @@ -385,4 +396,4 @@ - + \ No newline at end of file diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters index 910d93c32e..2c43296983 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj.filters +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -316,6 +316,18 @@ moc + + Settings + + + Settings + + + moc + + + moc + @@ -463,6 +475,8 @@ Debugger\Models + + @@ -587,6 +601,12 @@ Settings + + Settings + + + Settings + diff --git a/pcsx2-qt/resources/icons/black/svg/hammer-line.svg b/pcsx2-qt/resources/icons/black/svg/hammer-line.svg new file mode 100644 index 0000000000..f13f3c6e3e --- /dev/null +++ b/pcsx2-qt/resources/icons/black/svg/hammer-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/resources/icons/black/svg/tools-line.svg b/pcsx2-qt/resources/icons/black/svg/tools-line.svg new file mode 100644 index 0000000000..6fcaa9c685 --- /dev/null +++ b/pcsx2-qt/resources/icons/black/svg/tools-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/resources/icons/white/svg/hammer-line.svg b/pcsx2-qt/resources/icons/white/svg/hammer-line.svg new file mode 100644 index 0000000000..ced275373b --- /dev/null +++ b/pcsx2-qt/resources/icons/white/svg/hammer-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/resources/icons/white/svg/tools-line.svg b/pcsx2-qt/resources/icons/white/svg/tools-line.svg new file mode 100644 index 0000000000..14a5c7fb82 --- /dev/null +++ b/pcsx2-qt/resources/icons/white/svg/tools-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/resources/resources.qrc b/pcsx2-qt/resources/resources.qrc index cc681ca58b..a9d3163358 100644 --- a/pcsx2-qt/resources/resources.qrc +++ b/pcsx2-qt/resources/resources.qrc @@ -37,6 +37,7 @@ icons/black/svg/function-line.svg icons/black/svg/gamepad-line.svg icons/black/svg/global-line.svg + icons/black/svg/hammer-line.svg icons/black/svg/hard-drive-2-line.svg icons/black/svg/image-fill.svg icons/black/svg/keyboard-line.svg @@ -55,6 +56,7 @@ icons/black/svg/sd-card-line.svg icons/black/svg/settings-3-line.svg icons/black/svg/shut-down-line.svg + icons/black/svg/tools-line.svg icons/black/svg/trophy-line.svg icons/black/svg/tv-2-line.svg icons/black/svg/usb-fill.svg @@ -101,6 +103,7 @@ icons/white/svg/function-line.svg icons/white/svg/gamepad-line.svg icons/white/svg/global-line.svg + icons/white/svg/hammer-line.svg icons/white/svg/hard-drive-2-line.svg icons/white/svg/image-fill.svg icons/white/svg/keyboard-line.svg @@ -119,6 +122,7 @@ icons/white/svg/sd-card-line.svg icons/white/svg/settings-3-line.svg icons/white/svg/shut-down-line.svg + icons/white/svg/tools-line.svg icons/white/svg/trophy-line.svg icons/white/svg/tv-2-line.svg icons/white/svg/usb-fill.svg diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index d750d405ee..462ca37249 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -124,7 +124,6 @@ set(pcsx2Sources MultipartFileReader.cpp MultitapProtocol.cpp Patch.cpp - Patch_Memory.cpp Pcsx2Config.cpp PerformanceMetrics.cpp PrecompiledHeader.cpp diff --git a/pcsx2/Config.h b/pcsx2/Config.h index e06dd0d425..94a8dffbdc 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -623,6 +623,9 @@ struct Pcsx2Config static constexpr float DEFAULT_FRAME_RATE_NTSC = 59.94f; static constexpr float DEFAULT_FRAME_RATE_PAL = 50.00f; + static constexpr AspectRatioType DEFAULT_ASPECT_RATIO = AspectRatioType::RAuto4_3_3_2; + static constexpr GSInterlaceMode DEFAULT_INTERLACE_MODE = GSInterlaceMode::Automatic; + static constexpr int DEFAULT_VIDEO_CAPTURE_BITRATE = 6000; static constexpr int DEFAULT_VIDEO_CAPTURE_WIDTH = 640; static constexpr int DEFAULT_VIDEO_CAPTURE_HEIGHT = 480; @@ -719,9 +722,9 @@ struct Pcsx2Config float FramerateNTSC = DEFAULT_FRAME_RATE_NTSC; float FrameratePAL = DEFAULT_FRAME_RATE_PAL; - AspectRatioType AspectRatio = AspectRatioType::RAuto4_3_3_2; + AspectRatioType AspectRatio = DEFAULT_ASPECT_RATIO; FMVAspectRatioSwitchType FMVAspectRatioSwitch = FMVAspectRatioSwitchType::Off; - GSInterlaceMode InterlaceMode = GSInterlaceMode::Automatic; + GSInterlaceMode InterlaceMode = DEFAULT_INTERLACE_MODE; GSPostBilinearMode LinearPresent = GSPostBilinearMode::BilinearSmooth; float StretchY = 100.0f; @@ -1306,6 +1309,9 @@ struct Pcsx2Config void LoadSave(SettingsWrapper& wrap); void LoadSaveMemcards(SettingsWrapper& wrap); + /// Reloads options affected by patches. + void ReloadPatchAffectingOptions(); + std::string FullpathToBios() const; std::string FullpathToMcd(uint slot) const; @@ -1335,8 +1341,7 @@ namespace EmuFolders extern std::string Langs; extern std::string Logs; extern std::string Cheats; - extern std::string CheatsWS; - extern std::string CheatsNI; + extern std::string Patches; extern std::string Resources; extern std::string Cache; extern std::string Covers; diff --git a/pcsx2/GameDatabase.cpp b/pcsx2/GameDatabase.cpp index 99708f7fa2..69307bc82b 100644 --- a/pcsx2/GameDatabase.cpp +++ b/pcsx2/GameDatabase.cpp @@ -312,13 +312,13 @@ void GameDatabase::parseAndInsert(const std::string_view& serial, const c4::yml: { for (const auto& n : node["dynaPatches"].children()) { - DynamicPatch patch; + Patch::DynamicPatch patch; if (n.has_child("pattern") && n["pattern"].has_children()) { for (const auto& db_pattern : n["pattern"].children()) { - DynamicPatchEntry entry; + Patch::DynamicPatchEntry entry; db_pattern["offset"] >> entry.offset; db_pattern["value"] >> entry.value; @@ -326,7 +326,7 @@ void GameDatabase::parseAndInsert(const std::string_view& serial, const c4::yml: } for (const auto& db_replacement : n["replacement"].children()) { - DynamicPatchEntry entry; + Patch::DynamicPatchEntry entry; db_replacement["offset"] >> entry.offset; db_replacement["value"] >> entry.value; @@ -431,12 +431,12 @@ u32 GameDatabaseSchema::GameEntry::applyGameFixes(Pcsx2Config& config, bool appl { if (applyAuto) { - PatchesCon->WriteLn("(GameDB) Changing EE/FPU roundmode to %d [%s]", eeRM, EnumToString(eeRM)); + Console.WriteLn("(GameDB) Changing EE/FPU roundmode to %d [%s]", eeRM, EnumToString(eeRM)); config.Cpu.sseMXCSR.SetRoundMode(eeRM); num_applied_fixes++; } else - PatchesCon->Warning("[GameDB] Skipping changing EE/FPU roundmode to %d [%s]", eeRM, EnumToString(eeRM)); + Console.Warning("[GameDB] Skipping changing EE/FPU roundmode to %d [%s]", eeRM, EnumToString(eeRM)); } } @@ -447,12 +447,12 @@ u32 GameDatabaseSchema::GameEntry::applyGameFixes(Pcsx2Config& config, bool appl { if (applyAuto) { - PatchesCon->WriteLn("(GameDB) Changing VU0 roundmode to %d [%s]", vuRM, EnumToString(vuRM)); + Console.WriteLn("(GameDB) Changing VU0 roundmode to %d [%s]", vuRM, EnumToString(vuRM)); config.Cpu.sseVU0MXCSR.SetRoundMode(vuRM); num_applied_fixes++; } else - PatchesCon->Warning("[GameDB] Skipping changing VU0 roundmode to %d [%s]", vuRM, EnumToString(vuRM)); + Console.Warning("[GameDB] Skipping changing VU0 roundmode to %d [%s]", vuRM, EnumToString(vuRM)); } } @@ -463,12 +463,12 @@ u32 GameDatabaseSchema::GameEntry::applyGameFixes(Pcsx2Config& config, bool appl { if (applyAuto) { - PatchesCon->WriteLn("(GameDB) Changing VU1 roundmode to %d [%s]", vuRM, EnumToString(vuRM)); + Console.WriteLn("(GameDB) Changing VU1 roundmode to %d [%s]", vuRM, EnumToString(vuRM)); config.Cpu.sseVU1MXCSR.SetRoundMode(vuRM); num_applied_fixes++; } else - PatchesCon->Warning("[GameDB] Skipping changing VU1 roundmode to %d [%s]", vuRM, EnumToString(vuRM)); + Console.Warning("[GameDB] Skipping changing VU1 roundmode to %d [%s]", vuRM, EnumToString(vuRM)); } } @@ -477,14 +477,14 @@ u32 GameDatabaseSchema::GameEntry::applyGameFixes(Pcsx2Config& config, bool appl const int clampMode = enum_cast(eeClampMode); if (applyAuto) { - PatchesCon->WriteLn("(GameDB) Changing EE/FPU clamp mode [mode=%d]", clampMode); + Console.WriteLn("(GameDB) Changing EE/FPU clamp mode [mode=%d]", clampMode); config.Cpu.Recompiler.fpuOverflow = (clampMode >= 1); config.Cpu.Recompiler.fpuExtraOverflow = (clampMode >= 2); config.Cpu.Recompiler.fpuFullMode = (clampMode >= 3); num_applied_fixes++; } else - PatchesCon->Warning("[GameDB] Skipping changing EE/FPU clamp mode [mode=%d]", clampMode); + Console.Warning("[GameDB] Skipping changing EE/FPU clamp mode [mode=%d]", clampMode); } if (vu0ClampMode != GameDatabaseSchema::ClampMode::Undefined) @@ -492,14 +492,14 @@ u32 GameDatabaseSchema::GameEntry::applyGameFixes(Pcsx2Config& config, bool appl const int clampMode = enum_cast(vu0ClampMode); if (applyAuto) { - PatchesCon->WriteLn("(GameDB) Changing VU0 clamp mode [mode=%d]", clampMode); + Console.WriteLn("(GameDB) Changing VU0 clamp mode [mode=%d]", clampMode); config.Cpu.Recompiler.vu0Overflow = (clampMode >= 1); config.Cpu.Recompiler.vu0ExtraOverflow = (clampMode >= 2); config.Cpu.Recompiler.vu0SignOverflow = (clampMode >= 3); num_applied_fixes++; } else - PatchesCon->Warning("[GameDB] Skipping changing VU0 clamp mode [mode=%d]", clampMode); + Console.Warning("[GameDB] Skipping changing VU0 clamp mode [mode=%d]", clampMode); } if (vu1ClampMode != GameDatabaseSchema::ClampMode::Undefined) @@ -507,14 +507,14 @@ u32 GameDatabaseSchema::GameEntry::applyGameFixes(Pcsx2Config& config, bool appl const int clampMode = enum_cast(vu1ClampMode); if (applyAuto) { - PatchesCon->WriteLn("(GameDB) Changing VU1 clamp mode [mode=%d]", clampMode); + Console.WriteLn("(GameDB) Changing VU1 clamp mode [mode=%d]", clampMode); config.Cpu.Recompiler.vu1Overflow = (clampMode >= 1); config.Cpu.Recompiler.vu1ExtraOverflow = (clampMode >= 2); config.Cpu.Recompiler.vu1SignOverflow = (clampMode >= 3); num_applied_fixes++; } else - PatchesCon->Warning("[GameDB] Skipping changing VU1 clamp mode [mode=%d]", clampMode); + Console.Warning("[GameDB] Skipping changing VU1 clamp mode [mode=%d]", clampMode); } // TODO - config - this could be simplified with maps instead of bitfields and enums @@ -523,13 +523,13 @@ u32 GameDatabaseSchema::GameEntry::applyGameFixes(Pcsx2Config& config, bool appl const bool mode = it.second != 0; if (!applyAuto) { - PatchesCon->Warning("[GameDB] Skipping setting Speedhack '%s' to [mode=%d]", EnumToString(it.first), mode); + Console.Warning("[GameDB] Skipping setting Speedhack '%s' to [mode=%d]", EnumToString(it.first), mode); continue; } // Legacy note - speedhacks are setup in the GameDB as integer values, but // are effectively booleans like the gamefixes config.Speedhacks.Set(it.first, mode); - PatchesCon->WriteLn("(GameDB) Setting Speedhack '%s' to [mode=%d]", EnumToString(it.first), mode); + Console.WriteLn("(GameDB) Setting Speedhack '%s' to [mode=%d]", EnumToString(it.first), mode); num_applied_fixes++; } @@ -538,12 +538,12 @@ u32 GameDatabaseSchema::GameEntry::applyGameFixes(Pcsx2Config& config, bool appl { if (!applyAuto) { - PatchesCon->Warning("[GameDB] Skipping Gamefix: %s", EnumToString(id)); + Console.Warning("[GameDB] Skipping Gamefix: %s", EnumToString(id)); continue; } // if the fix is present, it is said to be enabled config.Gamefixes.Set(id, true); - PatchesCon->WriteLn("(GameDB) Enabled Gamefix: %s", EnumToString(id)); + Console.WriteLn("(GameDB) Enabled Gamefix: %s", EnumToString(id)); num_applied_fixes++; // The LUT is only used for 1 game so we allocate it only when the gamefix is enabled (save 4MB) @@ -685,7 +685,7 @@ u32 GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions& if (configMatchesHWFix(config, id, value)) continue; - PatchesCon->Warning("[GameDB] Skipping GS Hardware Fix: %s to [mode=%d]", getHWFixName(id), value); + Console.Warning("[GameDB] Skipping GS Hardware Fix: %s to [mode=%d]", getHWFixName(id), value); fmt::format_to(std::back_inserter(disabled_fixes), "{} {} = {}", disabled_fixes.empty() ? " " : "\n ", getHWFixName(id), value); continue; } @@ -900,7 +900,7 @@ u32 GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions& break; } - PatchesCon->WriteLn("[GameDB] Enabled GS Hardware Fix: %s to [mode=%d]", getHWFixName(id), value); + Console.WriteLn("[GameDB] Enabled GS Hardware Fix: %s to [mode=%d]", getHWFixName(id), value); num_applied_fixes++; } diff --git a/pcsx2/GameDatabase.h b/pcsx2/GameDatabase.h index 0930fd3889..0d235a6fa5 100644 --- a/pcsx2/GameDatabase.h +++ b/pcsx2/GameDatabase.h @@ -117,7 +117,7 @@ namespace GameDatabaseSchema std::vector> gsHWFixes; std::vector memcardFilters; std::unordered_map patches; - std::vector dynaPatches; + std::vector dynaPatches; // Returns the list of memory card serials as a `/` delimited string std::string memcardFiltersAsString() const; diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index bd17b52f26..f24b37a6aa 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -4098,8 +4098,7 @@ void FullscreenUI::DrawFoldersSettingsPage() DrawFolderSetting(bsi, ICON_FA_WRENCH " Game Settings Directory", "Folders", "GameSettings", EmuFolders::GameSettings); DrawFolderSetting(bsi, ICON_FA_GAMEPAD " Input Profile Directory", "Folders", "InputProfiles", EmuFolders::InputProfiles); DrawFolderSetting(bsi, ICON_FA_FROWN " Cheats Directory", "Folders", "Cheats", EmuFolders::Cheats); - DrawFolderSetting(bsi, ICON_FA_TV " Widescreen Cheats Directory", "Folders", "CheatsWS", EmuFolders::CheatsWS); - DrawFolderSetting(bsi, ICON_FA_MAGIC " No-Interlace Cheats Directory", "Folders", "CheatsNI", EmuFolders::CheatsNI); + DrawFolderSetting(bsi, ICON_FA_MAGIC " Patches Directory", "Folders", "Patches", EmuFolders::Patches); DrawFolderSetting(bsi, ICON_FA_SLIDERS_H "Texture Replacements Directory", "Folders", "Textures", EmuFolders::Textures); DrawFolderSetting(bsi, ICON_FA_SLIDERS_H "Video Dumping Directory", "Folders", "Videos", EmuFolders::Videos); diff --git a/pcsx2/Patch.cpp b/pcsx2/Patch.cpp index a3dd7af487..11d8dcf1b3 100644 --- a/pcsx2/Patch.cpp +++ b/pcsx2/Patch.cpp @@ -1,5 +1,5 @@ /* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2021 PCSX2 Dev Team + * Copyright (C) 2002-2023 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- @@ -17,65 +17,167 @@ #define _PC_ // disables MIPS opcode macros. +#include "common/ByteSwap.h" #include "common/FileSystem.h" #include "common/Path.h" #include "common/StringUtil.h" #include "common/ZipHelpers.h" +#include "Achievements.h" #include "Config.h" +#include "GameDatabase.h" +#include "Host.h" +#include "IopMem.h" +#include "Memory.h" #include "Patch.h" +#include "IconsFontAwesome5.h" +#include "fmt/format.h" +#include "gsl/span" + +#include +#include #include #include #include -// This is a declaration for PatchMemory.cpp::_ApplyPatch where we're (patch.cpp) -// the only consumer, so it's not made public via Patch.h -// Applies a single patch line to emulation memory regardless of its "place" value. -extern void _ApplyPatch(IniPatch* p); -extern void _ApplyDynaPatch(const DynamicPatch& patch, u32 address); - -static std::vector Patch; -static std::vector DynaPatch; - -struct PatchTextTable +namespace Patch { - int code; - const char* text; - PATCHTABLEFUNC* func; -}; - -static const PatchTextTable commands_patch[] = + enum patch_cpu_type { - {1, "author", PatchFunc::author}, - {2, "comment", PatchFunc::comment}, - {3, "patch", PatchFunc::patch}, - {0, nullptr, nullptr} // Array Terminator -}; + NO_CPU, + CPU_EE, + CPU_IOP + }; -static const PatchTextTable dataType[] = + enum patch_data_type { - {1, "byte", nullptr}, - {2, "short", nullptr}, - {3, "word", nullptr}, - {4, "double", nullptr}, - {5, "extended", nullptr}, - {6, "beshort", nullptr}, - {7, "beword", nullptr}, - {8, "bedouble", nullptr}, - {0, nullptr, nullptr} // Array Terminator -}; + NO_TYPE, + BYTE_T, + SHORT_T, + WORD_T, + DOUBLE_T, + EXTENDED_T, + SHORT_BE_T, + WORD_BE_T, + DOUBLE_BE_T + }; -static const PatchTextTable cpuCore[] = + struct PatchCommand { - {1, "EE", nullptr}, - {2, "IOP", nullptr}, - {0, nullptr, nullptr} // Array Terminator -}; + patch_data_type type; + patch_cpu_type cpu; + int placetopatch; + u32 addr; + u64 data; -// IniFile Functions. + bool operator==(const PatchCommand& p) const { return std::memcmp(this, &p, sizeof(*this)) == 0; } + bool operator!=(const PatchCommand& p) const { return std::memcmp(this, &p, sizeof(*this)) != 0; } + }; + static_assert(sizeof(PatchCommand) == 24, "IniPatch has no padding"); -static void inifile_trim(std::string& buffer) + struct PatchGroup + { + std::string name; + std::optional override_aspect_ratio; + std::optional override_interlace_mode; + std::vector patches; + }; + + struct PatchTextTable + { + int code; + const char* text; + void (*func)(PatchGroup* group, const std::string_view& cmd, const std::string_view& param); + }; + + using PatchList = std::vector; + using ActivePatchList = std::vector; + using EnablePatchList = std::vector; + + namespace PatchFunc + { + static void author(PatchGroup* group, const std::string_view& cmd, const std::string_view& param); + static void comment(PatchGroup* group, const std::string_view& cmd, const std::string_view& param); + static void patch(PatchGroup* group, const std::string_view& cmd, const std::string_view& param); + static void gsaspectratio(PatchGroup* group, const std::string_view& cmd, const std::string_view& param); + static void gsinterlacemode(PatchGroup* group, const std::string_view& cmd, const std::string_view& param); + } // namespace PatchFunc + + static void TrimPatchLine(std::string& buffer); + static int PatchTableExecute(PatchGroup* group, const std::string_view& lhs, const std::string_view& rhs, + const gsl::span& Table); + static void LoadPatchLine(PatchGroup* group, const std::string_view& line); + static u32 LoadPatchesFromString(PatchList* patch_list, const std::string& patch_file); + static bool OpenPatchesZip(); + static std::string GetPnachTemplate( + const std::string_view& serial, u32 crc, bool include_serial, bool add_wildcard); + static std::vector FindPatchFilesOnDisk(const std::string_view& serial, u32 crc, bool cheats); + + template + static void EnumeratePnachFiles(const std::string_view& serial, u32 crc, bool cheats, const F& f); + + static void ExtractPatchInfo(PatchInfoList* dst, const std::string& pnach_data, u32* num_unlabelled_patches); + static void ReloadEnabledLists(); + static u32 EnablePatches(const PatchList& patches, const EnablePatchList& enable_list); + + static void ApplyPatch(const PatchCommand* p); + static void ApplyDynaPatch(const DynamicPatch& patch, u32 address); + static void writeCheat(); + static void handle_extended_t(const PatchCommand* p); + + // Name of patches which will be auto-enabled based on global options. + static constexpr std::string_view WS_PATCH_NAME = "Widescreen 16:9"; + static constexpr std::string_view NI_PATCH_NAME = "No-Interlacing"; + static constexpr std::string_view PATCHES_ZIP_NAME = "patches.zip"; + + const char* PATCHES_CONFIG_SECTION = "Patches"; + const char* CHEATS_CONFIG_SECTION = "Cheats"; + const char* PATCH_ENABLE_CONFIG_KEY = "Enable"; + + static zip_t* s_patches_zip; + static PatchList s_gamedb_patches; + static PatchList s_game_patches; + static PatchList s_cheat_patches; + + static ActivePatchList s_active_patches; + static std::vector s_active_dynamic_patches; + static EnablePatchList s_enabled_cheats; + static EnablePatchList s_enabled_patches; + static u32 s_patches_crc; + static std::string s_patches_serial; + static std::optional s_override_aspect_ratio; + static std::optional s_override_interlace_mode; + + static const PatchTextTable s_patch_commands[] = { + {0, "author", &Patch::PatchFunc::author}, + {0, "comment", &Patch::PatchFunc::comment}, + {0, "patch", &Patch::PatchFunc::patch}, + {0, "gsaspectratio", &Patch::PatchFunc::gsaspectratio}, + {0, "gsinterlacemode", &Patch::PatchFunc::gsinterlacemode}, + {0, nullptr, nullptr}, + }; + + static const PatchTextTable s_type_commands[] = { + {BYTE_T, "byte", nullptr}, + {SHORT_T, "short", nullptr}, + {WORD_T, "word", nullptr}, + {DOUBLE_T, "double", nullptr}, + {EXTENDED_T, "extended", nullptr}, + {SHORT_BE_T, "beshort", nullptr}, + {WORD_BE_T, "beword", nullptr}, + {DOUBLE_BE_T, "bedouble", nullptr}, + {NO_TYPE, nullptr, nullptr}, + }; + + static const PatchTextTable s_cpu_commands[] = { + {CPU_EE, "EE", nullptr}, + {CPU_IOP, "IOP", nullptr}, + {NO_CPU, nullptr, nullptr}, + }; +} // namespace Patch + +void Patch::TrimPatchLine(std::string& buffer) { StringUtil::StripWhitespace(&buffer); if (std::strncmp(buffer.c_str(), "//", 2) == 0) @@ -90,7 +192,8 @@ static void inifile_trim(std::string& buffer) buffer.erase(pos); } -static int PatchTableExecute(const std::string_view& lhs, const std::string_view& rhs, const PatchTextTable* Table) +int Patch::PatchTableExecute(PatchGroup* group, const std::string_view& lhs, const std::string_view& rhs, + const gsl::span& Table) { int i = 0; @@ -99,7 +202,7 @@ static int PatchTableExecute(const std::string_view& lhs, const std::string_view if (lhs.compare(Table[i].text) == 0) { if (Table[i].func) - Table[i].func(lhs, rhs); + Table[i].func(group, lhs, rhs); break; } i++; @@ -109,216 +212,1162 @@ static int PatchTableExecute(const std::string_view& lhs, const std::string_view } // This routine is for executing the commands of the ini file. -static void inifile_command(const std::string& cmd) +void Patch::LoadPatchLine(PatchGroup* group, const std::string_view& line) { std::string_view key, value; - StringUtil::ParseAssignmentString(cmd, &key, &value); + StringUtil::ParseAssignmentString(line, &key, &value); - // Is this really what we want to be doing here? Seems like just leaving it empty/blank - // would make more sense... --air - if (value.empty()) - value = key; - - /*int code = */ PatchTableExecute(key, value, commands_patch); + PatchTableExecute(group, key, value, s_patch_commands); } -int LoadPatchesFromString(const std::string& patches) +u32 Patch::LoadPatchesFromString(PatchList* patch_list, const std::string& patch_file) { - const size_t before = Patch.size(); + const size_t before = patch_list->size(); - std::istringstream ss(patches); + PatchGroup current_patch_group; + + std::istringstream ss(patch_file); std::string line; while (std::getline(ss, line)) { - inifile_trim(line); - if (!line.empty()) - inifile_command(line); + TrimPatchLine(line); + if (line.empty()) + continue; + + if (line.front() == '[') + { + if (line.length() < 2 || line.back() != ']') + { + Console.Error(fmt::format("Malformed patch line: {}", line.c_str())); + continue; + } + + if (!current_patch_group.name.empty() || !current_patch_group.patches.empty()) + { + patch_list->push_back(std::move(current_patch_group)); + current_patch_group = {}; + } + + current_patch_group.name = line.substr(1, line.length() - 2); + continue; + } + + LoadPatchLine(¤t_patch_group, line); } - return static_cast(Patch.size() - before); + if (!current_patch_group.name.empty() || !current_patch_group.patches.empty()) + patch_list->push_back(std::move(current_patch_group)); + + return static_cast(patch_list->size() - before); } -void ForgetLoadedPatches() +bool Patch::OpenPatchesZip() { - Patch.clear(); - DynaPatch.clear(); -} + if (s_patches_zip) + return true; + + const std::string filename = Path::Combine(EmuFolders::Resources, PATCHES_ZIP_NAME); -// This routine loads patches from a zip file -// Returns number of patches loaded -// Note: does not reset previously loaded patches (use ForgetLoadedPatches() for that) -// Note: only load patches from the root folder of the zip -int LoadPatchesFromZip(const std::string& crc, const u8* zip_data, size_t zip_data_size) -{ zip_error ze = {}; - auto zf = zip_open_buffer_managed(zip_data, zip_data_size, ZIP_RDONLY, 0, &ze); - if (!zf) - return 0; + zip_source_t* zs = zip_source_file_create(filename.c_str(), 0, 0, &ze); + if (zs && !(s_patches_zip = zip_open_from_source(zs, ZIP_RDONLY, &ze))) + { + static bool warning_shown = false; + if (!warning_shown) + { + Host::AddIconOSDMessage("PatchesZipOpenWarning", ICON_FA_MICROCHIP, + fmt::format("Failed to open {}. Built-in game patches are not available.", PATCHES_ZIP_NAME), + Host::OSD_ERROR_DURATION); + warning_shown = true; + } - const std::string pnach_filename(crc + ".pnach"); - std::optional pnach_data(ReadFileInZipToString(zf.get(), pnach_filename.c_str())); - if (!pnach_data.has_value()) - return 0; + // have to clean up source + Console.Error("Failed to open %s: %s", filename.c_str(), zip_error_strerror(&ze)); + zip_source_free(zs); + return false; + } - PatchesCon->WriteLn(Color_Green, "Loading patch '%s' from archive.", pnach_filename.c_str()); - return LoadPatchesFromString(pnach_data.value()); + std::atexit([]() { zip_close(s_patches_zip); }); + return true; } - -// This routine loads patches from *.pnach files -// Returns number of patches loaded -// Note: does not reset previously loaded patches (use ForgetLoadedPatches() for that) -int LoadPatchesFromDir(const std::string& crc, const std::string& folder, const char* friendly_name, bool show_error_when_missing) +std::string Patch::GetPnachTemplate(const std::string_view& serial, u32 crc, bool include_serial, bool add_wildcard) { - if (!FileSystem::DirectoryExists(folder.c_str())) - { - Console.WriteLn(Color_Red, "The %s folder ('%s') is inaccessible. Skipping...", friendly_name, folder.c_str()); - return 0; - } + if (include_serial) + return fmt::format("{}_{:08X}{}.pnach", serial, crc, add_wildcard ? "*" : ""); + else + return fmt::format("{:08X}{}.pnach", crc, add_wildcard ? "*" : ""); +} +std::vector Patch::FindPatchFilesOnDisk(const std::string_view& serial, u32 crc, bool cheats) +{ FileSystem::FindResultsArray files; - FileSystem::FindFiles(folder.c_str(), StringUtil::StdStringFromFormat("*.pnach", crc.c_str()).c_str(), - FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES, &files); + FileSystem::FindFiles(cheats ? EmuFolders::Cheats.c_str() : EmuFolders::Patches.c_str(), + GetPnachTemplate(serial, crc, true, true).c_str(), FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES, + &files); - if (show_error_when_missing && files.empty()) + std::vector ret; + ret.reserve(files.size()); + + for (FILESYSTEM_FIND_DATA& fd : files) + ret.push_back(std::move(fd.FileName)); + + // and patches without serials + FileSystem::FindFiles(cheats ? EmuFolders::Cheats.c_str() : EmuFolders::Patches.c_str(), + GetPnachTemplate(serial, crc, false, true).c_str(), FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES, + &files); + ret.reserve(ret.size() + files.size()); + for (FILESYSTEM_FIND_DATA& fd : files) + ret.push_back(std::move(fd.FileName)); + + return ret; +} + +template +void Patch::EnumeratePnachFiles(const std::string_view& serial, u32 crc, bool cheats, const F& f) +{ + // Prefer files on disk over the zip. + std::vector disk_patch_files; + if (cheats || !Achievements::ChallengeModeActive()) + disk_patch_files = FindPatchFilesOnDisk(serial, crc, cheats); + + if (!disk_patch_files.empty()) { - PatchesCon->WriteLn(Color_Gray, "Not found %s file: %s" FS_OSPATH_SEPARATOR_STR "%s.pnach", - friendly_name, folder.c_str(), crc.c_str()); + for (const std::string& file : disk_patch_files) + { + std::optional contents = FileSystem::ReadFileToString(file.c_str()); + if (contents.has_value()) + f(std::move(file), std::move(contents.value())); + } + + return; } - int total_loaded = 0; + // Otherwise fall back to the zip. + if (cheats || !OpenPatchesZip()) + return; - for (const FILESYSTEM_FIND_DATA& fd : files) + // Prefer filename with serial. + std::string zip_filename = GetPnachTemplate(serial, crc, true, false); + std::optional pnach_data(ReadFileInZipToString(s_patches_zip, zip_filename.c_str())); + if (!pnach_data.has_value()) { - const std::string_view name(Path::GetFileName(fd.FileName)); - if (name.length() < crc.length() || StringUtil::Strncasecmp(name.data(), crc.c_str(), crc.size()) != 0) + zip_filename = GetPnachTemplate(serial, crc, false, false); + pnach_data = ReadFileInZipToString(s_patches_zip, zip_filename.c_str()); + } + if (pnach_data.has_value()) + f(std::move(zip_filename), std::move(pnach_data.value())); +} + +void Patch::ExtractPatchInfo(PatchInfoList* dst, const std::string& pnach_data, u32* num_unlabelled_patches) +{ + std::istringstream ss(pnach_data); + std::string line; + PatchInfo current_patch; + while (std::getline(ss, line)) + { + TrimPatchLine(line); + if (line.empty()) continue; - PatchesCon->WriteLn(Color_Green, "Found %s file: '%.*s'", friendly_name, static_cast(name.size()), name.data()); + const bool has_patch = !current_patch.name.empty(); - const std::optional pnach_data(FileSystem::ReadFileToString(fd.FileName.c_str())); - if (!pnach_data.has_value()) + if (line.length() > 2 && line.front() == '[' && line.back() == ']') + { + if (has_patch) + { + dst->push_back(std::move(current_patch)); + current_patch = {}; + } + + current_patch.name = line.substr(1, line.length() - 2); continue; + } - const int loaded = LoadPatchesFromString(pnach_data.value()); - total_loaded += loaded; + std::string_view key, value; + StringUtil::ParseAssignmentString(line, &key, &value); - PatchesCon->WriteLn((loaded ? Color_Green : Color_Gray), "Loaded %d %s from '%.*s'.", - loaded, friendly_name, static_cast(name.size()), name.data()); + // Just ignore other directives, who knows what rubbish people have in here. + // Use comment for description if it hasn't been otherwise specified. + if (key == "author") + current_patch.author = value; + else if (key == "description") + current_patch.description = value; + else if (key == "comment" && current_patch.description.empty()) + current_patch.description = value; + else if (key == "patch" && !has_patch && num_unlabelled_patches) + (*num_unlabelled_patches)++; } - PatchesCon->WriteLn((total_loaded ? Color_Green : Color_Gray), "Overall %d %s loaded", total_loaded, friendly_name); - return total_loaded; + // Last one. + if (!current_patch.name.empty()) + dst->push_back(std::move(current_patch)); +} + +std::string_view Patch::PatchInfo::GetNamePart() const +{ + const std::string::size_type pos = name.rfind('\\'); + std::string_view ret = name; + if (pos != std::string::npos) + ret = ret.substr(pos + 1); + return ret; +} + +std::string_view Patch::PatchInfo::GetNameParentPart() const +{ + const std::string::size_type pos = name.rfind('\\'); + std::string_view ret; + if (pos != std::string::npos) + ret = std::string_view(name).substr(0, pos); + return ret; +} + +Patch::PatchInfoList Patch::GetPatchInfo(const std::string& serial, u32 crc, bool cheats, u32* num_unlabelled_patches) +{ + PatchInfoList ret; + + if (num_unlabelled_patches) + *num_unlabelled_patches = 0; + + EnumeratePnachFiles(serial, crc, cheats, + [&ret, num_unlabelled_patches](const std::string& filename, const std::string& pnach_data) { + ExtractPatchInfo(&ret, pnach_data, num_unlabelled_patches); + }); + + return ret; +} + +void Patch::ReloadEnabledLists() +{ + if (EmuConfig.EnableCheats && !Achievements::ChallengeModeActive()) + s_enabled_cheats = Host::GetStringListSetting(CHEATS_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY); + else + s_enabled_cheats = {}; + + s_enabled_patches = Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY); + + // Name based matching for widescreen/NI settings. + if (EmuConfig.EnableWideScreenPatches) + { + if (std::none_of(s_enabled_patches.begin(), s_enabled_patches.end(), + [](const std::string& it) { return (it == WS_PATCH_NAME); })) + { + s_enabled_patches.emplace_back(WS_PATCH_NAME); + } + } + if (EmuConfig.EnableNoInterlacingPatches) + { + if (std::none_of(s_enabled_patches.begin(), s_enabled_patches.end(), + [](const std::string& it) { return (it == NI_PATCH_NAME); })) + { + s_enabled_patches.emplace_back(NI_PATCH_NAME); + } + } +} + +u32 Patch::EnablePatches(const PatchList& patches, const EnablePatchList& enable_list) +{ + u32 count = 0; + for (const PatchGroup& p : patches) + { + // For compatibility, we auto enable anything that's not labelled. + // Also for gamedb patches. + if (!p.name.empty() && std::find(enable_list.begin(), enable_list.end(), p.name) == enable_list.end()) + continue; + + if (!p.name.empty()) + Console.WriteLn(Color_Green, fmt::format("Enabled patch: '{}'", p.name)); + + for (const PatchCommand& ip : p.patches) + { + if (std::none_of(s_active_patches.begin(), s_active_patches.end(), + [&ip](const PatchCommand* op) { return (ip == *op); })) + { + s_active_patches.push_back(&ip); + } + } + + if (p.override_aspect_ratio.has_value()) + s_override_aspect_ratio = p.override_aspect_ratio; + if (p.override_interlace_mode.has_value()) + s_override_interlace_mode = p.override_interlace_mode; + + count++; + } + + return count; +} + +void Patch::ReloadPatches(std::string serial, u32 crc, bool force_reload_files, bool reload_enabled_list, bool verbose) +{ + const bool serial_changed = (s_patches_serial != serial); + s_patches_crc = crc; + s_patches_serial = std::move(serial); + + // Skip reloading gamedb patches if the serial hasn't changed. + if (serial_changed) + { + s_gamedb_patches.clear(); + + const GameDatabaseSchema::GameEntry* game = GameDatabase::findGame(s_patches_serial); + if (game) + { + const std::string* patches = game->findPatch(crc); + if (patches) + { + const u32 patch_count = LoadPatchesFromString(&s_gamedb_patches, *patches); + if (patch_count > 0) + Console.WriteLn(Color_Green, "(GameDB) Patches Loaded: %d", patch_count); + } + + LoadDynamicPatches(game->dynaPatches); + } + } + + ReloadPatches(serial_changed, reload_enabled_list, verbose); +} + +void Patch::ReloadPatches(bool force_reload_files, bool reload_enabled_list, bool verbose) +{ + if (force_reload_files) + { + s_game_patches.clear(); + EnumeratePnachFiles( + s_patches_serial, s_patches_crc, false, [](const std::string& filename, const std::string& pnach_data) { + const u32 patch_count = LoadPatchesFromString(&s_game_patches, pnach_data); + if (patch_count > 0) + Console.WriteLn(Color_Green, fmt::format("Loaded {} game patches from {}.", patch_count, filename)); + }); + + s_cheat_patches.clear(); + EnumeratePnachFiles( + s_patches_serial, s_patches_crc, true, [](const std::string& filename, const std::string& pnach_data) { + const u32 patch_count = LoadPatchesFromString(&s_cheat_patches, pnach_data); + if (patch_count > 0) + Console.WriteLn(Color_Green, fmt::format("Loaded {} cheats from {}.", patch_count, filename)); + }); + } + + UpdateActivePatches(reload_enabled_list, verbose, false); +} + +void Patch::UpdateActivePatches(bool reload_enabled_list, bool verbose, bool verbose_if_changed) +{ + if (reload_enabled_list) + ReloadEnabledLists(); + + const size_t prev_count = s_active_patches.size(); + s_active_patches.clear(); + s_override_aspect_ratio.reset(); + s_override_interlace_mode.reset(); + + std::string message; + if (EmuConfig.EnablePatches) + { + const u32 gp_count = EnablePatches(s_gamedb_patches, EnablePatchList()); + if (gp_count > 0) + fmt::format_to(std::back_inserter(message), "{} GameDB patches", gp_count); + } + + const u32 p_count = EnablePatches(s_game_patches, s_enabled_patches); + if (p_count > 0) + fmt::format_to(std::back_inserter(message), "{}{} game patches", message.empty() ? "" : ", ", p_count); + + const u32 c_count = EmuConfig.EnableCheats ? EnablePatches(s_cheat_patches, s_enabled_cheats) : 0; + if (c_count > 0) + fmt::format_to(std::back_inserter(message), "{}{} cheat patches", message.empty() ? "" : ", ", c_count); + + // Display message on first boot when we load patches. + if (verbose || (verbose_if_changed && prev_count != s_active_patches.size())) + { + if (!message.empty()) + { + fmt::format_to(std::back_inserter(message), " are active."); + Host::AddIconOSDMessage("LoadPatches", ICON_FA_FILE_CODE, std::move(message), Host::OSD_INFO_DURATION); + } + else + { + Host::AddIconOSDMessage("LoadPatches", ICON_FA_FILE_CODE, + "No cheats or patches (widescreen, compatibility or others) are found / enabled.", + Host::OSD_INFO_DURATION); + } + } +} + +void Patch::ApplyPatchSettingOverrides() +{ + // Switch to 16:9 if widescreen patches are enabled, and AR is auto. + if (s_override_aspect_ratio.has_value() && EmuConfig.GS.AspectRatio == AspectRatioType::RAuto4_3_3_2) + { + // Don't change when reloading settings in the middle of a FMV with switch. + if (EmuConfig.CurrentAspectRatio == EmuConfig.GS.AspectRatio) + EmuConfig.CurrentAspectRatio = s_override_aspect_ratio.value(); + + Console.WriteLn(Color_Gray, + fmt::format("Patch: Setting aspect ratio to {} by patch request.", + Pcsx2Config::GSOptions::AspectRatioNames[static_cast(s_override_aspect_ratio.value())])); + EmuConfig.GS.AspectRatio = s_override_aspect_ratio.value(); + } + + // Disable interlacing in GS if active. + if (s_override_interlace_mode.has_value() && EmuConfig.GS.InterlaceMode == GSInterlaceMode::Automatic) + { + Console.WriteLn(Color_Gray, fmt::format("Patch: Setting deinterlace mode to {} by patch request.", + static_cast(s_override_interlace_mode.value()))); + EmuConfig.GS.InterlaceMode = s_override_interlace_mode.value(); + } +} + +bool Patch::ReloadPatchAffectingOptions() +{ + const AspectRatioType current_ar = EmuConfig.GS.AspectRatio; + const GSInterlaceMode current_interlace = EmuConfig.GS.InterlaceMode; + + // This is pretty gross, but we're not using a config layer, so... + AspectRatioType new_ar = Pcsx2Config::GSOptions::DEFAULT_ASPECT_RATIO; + const std::string ar_value = Host::GetStringSettingValue("EmuCore/GS", "AspectRatio", + Pcsx2Config::GSOptions::AspectRatioNames[static_cast(EmuConfig.GS.AspectRatio)]); + for (u32 i = 0; i < static_cast(AspectRatioType::MaxCount); i++) + { + if (ar_value == Pcsx2Config::GSOptions::AspectRatioNames[i]) + { + new_ar = static_cast(i); + break; + } + } + if (EmuConfig.CurrentAspectRatio == EmuConfig.GS.AspectRatio) + EmuConfig.CurrentAspectRatio = new_ar; + EmuConfig.GS.AspectRatio = new_ar; + EmuConfig.GS.InterlaceMode = static_cast(Host::GetIntSettingValue( + "EmuCore/GS", "deinterlace_mode", static_cast(Pcsx2Config::GSOptions::DEFAULT_INTERLACE_MODE))); + + ApplyPatchSettingOverrides(); + + return (current_ar != EmuConfig.GS.AspectRatio || current_interlace != EmuConfig.GS.InterlaceMode); +} + +void Patch::UnloadPatches() +{ + s_override_interlace_mode = {}; + s_override_aspect_ratio = {}; + s_patches_crc = 0; + s_patches_serial = {}; + s_active_patches = {}; + s_active_dynamic_patches = {}; + s_enabled_patches = {}; + s_enabled_cheats = {}; + s_cheat_patches = {}; + s_game_patches = {}; + s_gamedb_patches = {}; } // PatchFunc Functions. -namespace PatchFunc +void Patch::PatchFunc::comment(PatchGroup* group, const std::string_view& cmd, const std::string_view& param) { - void comment(const std::string_view& text1, const std::string_view& text2) + Console.WriteLn(fmt::format("Patch comment: {}", param)); +} + +void Patch::PatchFunc::author(PatchGroup* group, const std::string_view& cmd, const std::string_view& param) +{ + Console.WriteLn(fmt::format("Patch author: {}", param)); +} + +void Patch::PatchFunc::patch(PatchGroup* group, const std::string_view& cmd, const std::string_view& param) +{ + // print the actual patch lines only in verbose mode (even in devel) + if (DevConWriterEnabled) + DevCon.WriteLn(fmt::format("{} {}", cmd, param)); + +#define PATCH_ERROR(fstring, ...) \ + Console.Error(fmt::format("(Patch) Error Parsing: {}={}: " fstring, cmd, param, __VA_ARGS__)) + + // [0]=PlaceToPatch,[1]=CpuType,[2]=MemAddr,[3]=OperandSize,[4]=WriteValue + const std::vector pieces(StringUtil::SplitString(param, ',', false)); + if (pieces.size() != 5) { - PatchesCon->WriteLn("comment: %.*s", static_cast(text2.length()), text2.data()); + PATCH_ERROR("Expected 5 data parameters; only found {}", pieces.size()); + return; } - void author(const std::string_view& text1, const std::string_view& text2) + std::string_view placetopatch_end; + const std::optional placetopatch = StringUtil::FromChars(pieces[0], 10, &placetopatch_end); + + PatchCommand iPatch; + iPatch.placetopatch = StringUtil::FromChars(pieces[0]).value_or(PPT_END_MARKER); + + if (!placetopatch.has_value() || !placetopatch_end.empty() || + (iPatch.placetopatch = placetopatch.value()) >= PPT_END_MARKER) { - PatchesCon->WriteLn("Author: %.*s", static_cast(text2.length()), text2.data()); + PATCH_ERROR("Invalid 'place' value '{}' (0 - once on startup, 1: continuously)", pieces[0]); + return; } - void patch(const std::string_view& cmd, const std::string_view& param) + std::string_view addr_end, data_end; + const std::optional addr = StringUtil::FromChars(pieces[2], 16, &addr_end); + const std::optional data = StringUtil::FromChars(pieces[4], 16, &data_end); + if (!addr.has_value() || !addr_end.empty()) { - // print the actual patch lines only in verbose mode (even in devel) - if (DevConWriterEnabled) - { - DevCon.WriteLn("%.*s %.*s", static_cast(cmd.size()), cmd.data(), - static_cast(param.size()), param.data()); - } + PATCH_ERROR("Malformed address '{}', a hex number without prefix (e.g. 0123ABCD) is expected", pieces[2]); + return; + } + else if (!data.has_value() || !data_end.empty()) + { + PATCH_ERROR("Malformed data '{}', a hex number without prefix (e.g. 0123ABCD) is expected", pieces[4]); + return; + } -#define PATCH_ERROR(fmt, ...) Console.Error("(Patch) Error Parsing: %.*s=%.*s: " fmt, \ - static_cast(cmd.size()), cmd.data(), static_cast(param.size()), param.data(), \ - __VA_ARGS__) + iPatch.cpu = (patch_cpu_type)PatchTableExecute(group, pieces[1], std::string_view(), s_cpu_commands); + iPatch.addr = addr.value(); + iPatch.type = (patch_data_type)PatchTableExecute(group, pieces[3], std::string_view(), s_type_commands); + iPatch.data = data.value(); - // [0]=PlaceToPatch,[1]=CpuType,[2]=MemAddr,[3]=OperandSize,[4]=WriteValue - const std::vector pieces(StringUtil::SplitString(param, ',', false)); - if (pieces.size() != 5) - { - PATCH_ERROR("Expected 5 data parameters; only found %zu", pieces.size()); - return; - } + if (iPatch.cpu == 0) + { + PATCH_ERROR("Unrecognized CPU Target: '%.*s'", pieces[1]); + return; + } - std::string_view placetopatch_end; - const std::optional placetopatch = StringUtil::FromChars(pieces[0], 10, &placetopatch_end); + if (iPatch.type == 0) + { + PATCH_ERROR("Unrecognized Operand Size: '%.*s'", pieces[3]); + return; + } - IniPatch iPatch = {0}; - iPatch.enabled = 0; - iPatch.placetopatch = StringUtil::FromChars(pieces[0]).value_or(_PPT_END_MARKER); - - if (!placetopatch.has_value() || !placetopatch_end.empty() || - (iPatch.placetopatch = placetopatch.value()) >= _PPT_END_MARKER) - { - PATCH_ERROR("Invalid 'place' value '%.*s' (0 - once on startup, 1: continuously)", - static_cast(pieces[0].size()), pieces[0].data()); - return; - } - - std::string_view addr_end, data_end; - const std::optional addr = StringUtil::FromChars(pieces[2], 16, &addr_end); - const std::optional data = StringUtil::FromChars(pieces[4], 16, &data_end); - if (!addr.has_value() || !addr_end.empty()) - { - PATCH_ERROR("Malformed address '%.*s', a hex number without prefix (e.g. 0123ABCD) is expected", static_cast(pieces[2].size()), pieces[2].data()); - return; - } - else if (!data.has_value() || !data_end.empty()) - { - PATCH_ERROR("Malformed data '%.*s', a hex number without prefix (e.g. 0123ABCD) is expected", static_cast(pieces[4].size()), pieces[4].data()); - return; - } - - iPatch.cpu = (patch_cpu_type)PatchTableExecute(pieces[1], std::string_view(), cpuCore); - iPatch.addr = addr.value(); - iPatch.type = (patch_data_type)PatchTableExecute(pieces[3], std::string_view(), dataType); - iPatch.data = data.value(); - - if (iPatch.cpu == 0) - { - PATCH_ERROR("Unrecognized CPU Target: '%.*s'", static_cast(pieces[1].size()), pieces[1].data()); - return; - } - - if (iPatch.type == 0) - { - PATCH_ERROR("Unrecognized Operand Size: '%.*s'", static_cast(pieces[3].size()), pieces[3].data()); - return; - } - - iPatch.enabled = 1; - Patch.push_back(iPatch); + group->patches.push_back(iPatch); #undef PATCH_ERROR +} + +void Patch::PatchFunc::gsaspectratio(PatchGroup* group, const std::string_view& cmd, const std::string_view& param) +{ + for (u32 i = 0; i < static_cast(AspectRatioType::MaxCount); i++) + { + if (param == Pcsx2Config::GSOptions::AspectRatioNames[i]) + { + group->override_aspect_ratio = static_cast(i); + return; + } } -} // namespace PatchFunc + + Console.Error(fmt::format("Patch error: {} is an unknown aspect ratio.", param)); +} + +void Patch::PatchFunc::gsinterlacemode(PatchGroup* group, const std::string_view& cmd, const std::string_view& param) +{ + const std::optional interlace_mode = StringUtil::FromChars(param); + if (!interlace_mode.has_value() || interlace_mode.value() < 0 || + interlace_mode.value() >= static_cast(GSInterlaceMode::Count)) + { + Console.Error(fmt::format("Patch error: {} is an unknown interlace mode.", param)); + return; + } + + group->override_interlace_mode = static_cast(interlace_mode.value()); +} // This is for applying patches directly to memory -void ApplyLoadedPatches(patch_place_type place) +void Patch::ApplyLoadedPatches(patch_place_type place) { - for (auto& i : Patch) + for (const PatchCommand* i : s_active_patches) { - if (i.placetopatch == place) - _ApplyPatch(&i); + if (i->placetopatch == place) + ApplyPatch(i); } } -void ApplyDynamicPatches(u32 pc) +void Patch::ApplyDynamicPatches(u32 pc) { - for (const auto& dynpatch : DynaPatch) + for (const auto& dynpatch : s_active_dynamic_patches) + ApplyDynaPatch(dynpatch, pc); +} + +void Patch::LoadDynamicPatches(const std::vector& patches) +{ + for (const DynamicPatch& it : patches) + s_active_dynamic_patches.push_back(it); +} + +static u32 SkipCount = 0, IterationCount = 0; +static u32 IterationIncrement = 0, ValueIncrement = 0; +static u32 PrevCheatType = 0, PrevCheatAddr = 0, LastType = 0; + +void Patch::writeCheat() +{ + switch (LastType) { - _ApplyDynaPatch(dynpatch, pc); + case 0x0: + memWrite8(PrevCheatAddr, IterationIncrement & 0xFF); + break; + case 0x1: + memWrite16(PrevCheatAddr, IterationIncrement & 0xFFFF); + break; + case 0x2: + memWrite32(PrevCheatAddr, IterationIncrement); + break; + default: + break; } } -void LoadDynamicPatches(const std::vector& patches) +void Patch::handle_extended_t(const PatchCommand* p) { - for (const DynamicPatch& it : patches){ - DynaPatch.push_back(it); + if (SkipCount > 0) + { + SkipCount--; + } + else + switch (PrevCheatType) + { + case 0x3040: // vvvvvvvv 00000000 Inc + { + u32 mem = memRead32(PrevCheatAddr); + memWrite32(PrevCheatAddr, mem + (p->addr)); + PrevCheatType = 0; + break; + } + + case 0x3050: // vvvvvvvv 00000000 Dec + { + u32 mem = memRead32(PrevCheatAddr); + memWrite32(PrevCheatAddr, mem - (p->addr)); + PrevCheatType = 0; + break; + } + + case 0x4000: // vvvvvvvv iiiiiiii + for (u32 i = 0; i < IterationCount; i++) + { + memWrite32((u32)(PrevCheatAddr + (i * IterationIncrement)), (u32)(p->addr + ((u32)p->data * i))); + } + PrevCheatType = 0; + break; + + case 0x5000: // bbbbbbbb 00000000 + for (u32 i = 0; i < IterationCount; i++) + { + u8 mem = memRead8(PrevCheatAddr + i); + memWrite8((p->addr + i) & 0x0FFFFFFF, mem); + } + PrevCheatType = 0; + break; + + case 0x6000: // 000Xnnnn iiiiiiii + { + // Get Number of pointers + if (((u32)p->addr & 0x0000FFFF) == 0) + IterationCount = 1; + else + IterationCount = (u32)p->addr & 0x0000FFFF; + + // Read first pointer + LastType = ((u32)p->addr & 0x000F0000) >> 16; + u32 mem = memRead32(PrevCheatAddr); + + PrevCheatAddr = mem + (u32)p->data; + IterationCount--; + + // Check if needed to read another pointer + if (IterationCount == 0) + { + PrevCheatType = 0; + if (((mem & 0x0FFFFFFF) & 0x3FFFFFFC) != 0) + writeCheat(); + } + else + { + if (((mem & 0x0FFFFFFF) & 0x3FFFFFFC) == 0) + PrevCheatType = 0; + else + PrevCheatType = 0x6001; + } + } + break; + + case 0x6001: // 000Xnnnn iiiiiiii + { + // Read first pointer + u32 mem = memRead32(PrevCheatAddr & 0x0FFFFFFF); + + PrevCheatAddr = mem + (u32)p->addr; + IterationCount--; + + // Check if needed to read another pointer + if (IterationCount == 0) + { + PrevCheatType = 0; + if (((mem & 0x0FFFFFFF) & 0x3FFFFFFC) != 0) + writeCheat(); + } + else + { + mem = memRead32(PrevCheatAddr); + + PrevCheatAddr = mem + (u32)p->data; + IterationCount--; + if (IterationCount == 0) + { + PrevCheatType = 0; + if (((mem & 0x0FFFFFFF) & 0x3FFFFFFC) != 0) + writeCheat(); + } + } + } + break; + + default: + if ((p->addr & 0xF0000000) == 0x00000000) // 0aaaaaaa 0000000vv + { + memWrite8(p->addr & 0x0FFFFFFF, (u8)p->data & 0x000000FF); + PrevCheatType = 0; + } + else if ((p->addr & 0xF0000000) == 0x10000000) // 1aaaaaaa 0000vvvv + { + memWrite16(p->addr & 0x0FFFFFFF, (u16)p->data & 0x0000FFFF); + PrevCheatType = 0; + } + else if ((p->addr & 0xF0000000) == 0x20000000) // 2aaaaaaa vvvvvvvv + { + memWrite32(p->addr & 0x0FFFFFFF, (u32)p->data); + PrevCheatType = 0; + } + else if ((p->addr & 0xFFFF0000) == 0x30000000) // 300000vv 0aaaaaaa Inc + { + u8 mem = memRead8((u32)p->data); + memWrite8((u32)p->data, mem + (p->addr & 0x000000FF)); + PrevCheatType = 0; + } + else if ((p->addr & 0xFFFF0000) == 0x30100000) // 301000vv 0aaaaaaa Dec + { + u8 mem = memRead8((u32)p->data); + memWrite8((u32)p->data, mem - (p->addr & 0x000000FF)); + PrevCheatType = 0; + } + else if ((p->addr & 0xFFFF0000) == 0x30200000) // 3020vvvv 0aaaaaaa Inc + { + u16 mem = memRead16((u32)p->data); + memWrite16((u32)p->data, mem + (p->addr & 0x0000FFFF)); + PrevCheatType = 0; + } + else if ((p->addr & 0xFFFF0000) == 0x30300000) // 3030vvvv 0aaaaaaa Dec + { + u16 mem = memRead16((u32)p->data); + memWrite16((u32)p->data, mem - (p->addr & 0x0000FFFF)); + PrevCheatType = 0; + } + else if ((p->addr & 0xFFFF0000) == 0x30400000) // 30400000 0aaaaaaa Inc + Another line + { + PrevCheatType = 0x3040; + PrevCheatAddr = (u32)p->data; + } + else if ((p->addr & 0xFFFF0000) == 0x30500000) // 30500000 0aaaaaaa Inc + Another line + { + PrevCheatType = 0x3050; + PrevCheatAddr = (u32)p->data; + } + else if ((p->addr & 0xF0000000) == 0x40000000) // 4aaaaaaa nnnnssss + Another line + { + IterationCount = ((u32)p->data & 0xFFFF0000) >> 16; + IterationIncrement = ((u32)p->data & 0x0000FFFF) * 4; + PrevCheatAddr = (u32)p->addr & 0x0FFFFFFF; + PrevCheatType = 0x4000; + } + else if ((p->addr & 0xF0000000) == 0x50000000) // 5sssssss nnnnnnnn + Another line + { + PrevCheatAddr = (u32)p->addr & 0x0FFFFFFF; + IterationCount = ((u32)p->data); + PrevCheatType = 0x5000; + } + else if ((p->addr & 0xF0000000) == 0x60000000) // 6aaaaaaa 000000vv + Another line/s + { + PrevCheatAddr = (u32)p->addr & 0x0FFFFFFF; + IterationIncrement = ((u32)p->data); + IterationCount = 0; + PrevCheatType = 0x6000; + } + else if ((p->addr & 0xF0000000) == 0x70000000) + { + if ((p->data & 0x00F00000) == 0x00000000) // 7aaaaaaa 000000vv + { + u8 mem = memRead8((u32)p->addr & 0x0FFFFFFF); + memWrite8((u32)p->addr & 0x0FFFFFFF, (u8)(mem | (p->data & 0x000000FF))); + } + else if ((p->data & 0x00F00000) == 0x00100000) // 7aaaaaaa 0010vvvv + { + u16 mem = memRead16((u32)p->addr & 0x0FFFFFFF); + memWrite16((u32)p->addr & 0x0FFFFFFF, (u16)(mem | (p->data & 0x0000FFFF))); + } + else if ((p->data & 0x00F00000) == 0x00200000) // 7aaaaaaa 002000vv + { + u8 mem = memRead8((u32)p->addr & 0x0FFFFFFF); + memWrite8((u32)p->addr & 0x0FFFFFFF, (u8)(mem & (p->data & 0x000000FF))); + } + else if ((p->data & 0x00F00000) == 0x00300000) // 7aaaaaaa 0030vvvv + { + u16 mem = memRead16((u32)p->addr & 0x0FFFFFFF); + memWrite16((u32)p->addr & 0x0FFFFFFF, (u16)(mem & (p->data & 0x0000FFFF))); + } + else if ((p->data & 0x00F00000) == 0x00400000) // 7aaaaaaa 004000vv + { + u8 mem = memRead8((u32)p->addr & 0x0FFFFFFF); + memWrite8((u32)p->addr & 0x0FFFFFFF, (u8)(mem ^ (p->data & 0x000000FF))); + } + else if ((p->data & 0x00F00000) == 0x00500000) // 7aaaaaaa 0050vvvv + { + u16 mem = memRead16((u32)p->addr & 0x0FFFFFFF); + memWrite16((u32)p->addr & 0x0FFFFFFF, (u16)(mem ^ (p->data & 0x0000FFFF))); + } + } + else if ((p->addr & 0xF0000000) == 0xD0000000 || (p->addr & 0xF0000000) == 0xE0000000) + { + u32 addr = (u32)p->addr; + u32 data = (u32)p->data; + + // Since D-codes now have the additional functionality present in PS2rd which + // incorporates E-code-like functionality by making use of the unused bits in + // D-codes, the E-codes are now just converted to D-codes to reduce bloat. + + if ((addr & 0xF0000000) == 0xE0000000) + { + // Ezyyvvvv taaaaaaa -> Daaaaaaa yytzvvvv + addr = 0xD0000000 | ((u32)p->data & 0x0FFFFFFF); + data = 0x00000000 | ((u32)p->addr & 0x0000FFFF); + data = data | ((u32)p->addr & 0x00FF0000) << 8; + data = data | ((u32)p->addr & 0x0F000000) >> 8; + data = data | ((u32)p->data & 0xF0000000) >> 8; + } + + const u8 type = (data & 0x000F0000) >> 16; + const u8 cond = (data & 0x00F00000) >> 20; + + if (cond == 0) // Daaaaaaa yy0zvvvv + { + if (type == 0) // Daaaaaaa yy00vvvv + { + u16 mem = memRead16(addr & 0x0FFFFFFF); + if (mem != (data & 0x0000FFFF)) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + else if (type == 1) // Daaaaaaa yy0100vv + { + u8 mem = memRead8(addr & 0x0FFFFFFF); + if (mem != (data & 0x000000FF)) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + } + else if (cond == 1) // Daaaaaaa yy1zvvvv + { + if (type == 0) // Daaaaaaa yy10vvvv + { + u16 mem = memRead16(addr & 0x0FFFFFFF); + if (mem == (data & 0x0000FFFF)) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + else if (type == 1) // Daaaaaaa yy1100vv + { + u8 mem = memRead8(addr & 0x0FFFFFFF); + if (mem == (data & 0x000000FF)) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + } + else if (cond == 2) // Daaaaaaa yy2zvvvv + { + if (type == 0) // Daaaaaaa yy20vvvv + { + u16 mem = memRead16(addr & 0x0FFFFFFF); + if (mem >= (data & 0x0000FFFF)) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + else if (type == 1) // Daaaaaaa yy2100vv + { + u8 mem = memRead8(addr & 0x0FFFFFFF); + if (mem >= (data & 0x000000FF)) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + } + else if (cond == 3) // Daaaaaaa yy3zvvvv + { + if (type == 0) // Daaaaaaa yy30vvvv + { + u16 mem = memRead16(addr & 0x0FFFFFFF); + if (mem <= (data & 0x0000FFFF)) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + else if (type == 1) // Daaaaaaa yy3100vv + { + u8 mem = memRead8(addr & 0x0FFFFFFF); + if (mem <= (data & 0x000000FF)) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + } + else if (cond == 4) // Daaaaaaa yy4zvvvv + { + if (type == 0) // Daaaaaaa yy40vvvv + { + u16 mem = memRead16(addr & 0x0FFFFFFF); + if (mem & (data & 0x0000FFFF)) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + else if (type == 1) // Daaaaaaa yy4100vv + { + u8 mem = memRead8(addr & 0x0FFFFFFF); + if (mem & (data & 0x000000FF)) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + } + else if (cond == 5) // Daaaaaaa yy5zvvvv + { + if (type == 0) // Daaaaaaa yy50vvvv + { + u16 mem = memRead16(addr & 0x0FFFFFFF); + if (!(mem & (data & 0x0000FFFF))) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + else if (type == 1) // Daaaaaaa yy5100vv + { + u8 mem = memRead8(addr & 0x0FFFFFFF); + if (!(mem & (data & 0x000000FF))) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + } + else if (cond == 6) // Daaaaaaa yy6zvvvv + { + if (type == 0) // Daaaaaaa yy60vvvv + { + u16 mem = memRead16(addr & 0x0FFFFFFF); + if (mem | (data & 0x0000FFFF)) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + else if (type == 1) // Daaaaaaa yy6100vv + { + u8 mem = memRead8(addr & 0x0FFFFFFF); + if (mem | (data & 0x000000FF)) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + } + else if (cond == 7) // Daaaaaaa yy7zvvvv + { + if (type == 0) // Daaaaaaa yy70vvvv + { + u16 mem = memRead16(addr & 0x0FFFFFFF); + if (!(mem | (data & 0x0000FFFF))) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + else if (type == 1) // Daaaaaaa yy7100vv + { + u8 mem = memRead8(addr & 0x0FFFFFFF); + if (!(mem | (data & 0x000000FF))) + { + SkipCount = (data & 0xFF000000) >> 24; + if (!SkipCount) + { + SkipCount = 1; + } + } + PrevCheatType = 0; + } + } + } + } +} + +void Patch::ApplyPatch(const PatchCommand* p) +{ + u64 ledata = 0; + + switch (p->cpu) + { + case CPU_EE: + switch (p->type) + { + case BYTE_T: + if (memRead8(p->addr) != (u8)p->data) + memWrite8(p->addr, (u8)p->data); + break; + + case SHORT_T: + if (memRead16(p->addr) != (u16)p->data) + memWrite16(p->addr, (u16)p->data); + break; + + case WORD_T: + if (memRead32(p->addr) != (u32)p->data) + memWrite32(p->addr, (u32)p->data); + break; + + case DOUBLE_T: + if (memRead64(p->addr) != (u64)p->data) + memWrite64(p->addr, (u64)p->data); + break; + + case EXTENDED_T: + handle_extended_t(p); + break; + + case SHORT_BE_T: + ledata = ByteSwap(static_cast(p->data)); + if (memRead16(p->addr) != (u16)ledata) + memWrite16(p->addr, (u16)ledata); + break; + + case WORD_BE_T: + ledata = ByteSwap(static_cast(p->data)); + if (memRead32(p->addr) != (u32)ledata) + memWrite32(p->addr, (u32)ledata); + break; + + case DOUBLE_BE_T: + ledata = ByteSwap(p->data); + if (memRead64(p->addr) != (u64)ledata) + memWrite64(p->addr, (u64)ledata); + break; + + default: + break; + } + break; + + case CPU_IOP: + switch (p->type) + { + case BYTE_T: + if (iopMemRead8(p->addr) != (u8)p->data) + iopMemWrite8(p->addr, (u8)p->data); + break; + case SHORT_T: + if (iopMemRead16(p->addr) != (u16)p->data) + iopMemWrite16(p->addr, (u16)p->data); + break; + case WORD_T: + if (iopMemRead32(p->addr) != (u32)p->data) + iopMemWrite32(p->addr, (u32)p->data); + break; + default: + break; + } + break; + + default: + break; + } +} + +void Patch::ApplyDynaPatch(const DynamicPatch& patch, u32 address) +{ + for (const auto& pattern : patch.pattern) + { + if (*static_cast(PSM(address + pattern.offset)) != pattern.value) + return; + } + + Console.WriteLn("Applying Dynamic Patch to address 0x%08X", address); + // If everything passes, apply the patch. + for (const auto& replacement : patch.replacement) + { + memWrite32(address + replacement.offset, replacement.value); } } diff --git a/pcsx2/Patch.h b/pcsx2/Patch.h index f195845e7b..9e9604ddd8 100644 --- a/pcsx2/Patch.h +++ b/pcsx2/Patch.h @@ -28,121 +28,91 @@ // - UI name: "Patches", controlled via system -> enable automatic game fixes // - note that automatic game fixes also controls automatic config changes from GameIndex.dbf (UI name: "fixes") // -// So, if the console title contains the following suffix: "... [2 Fixes] [1 Patches] [0 Cheats] [6 widescreen hacks]" then: -// - The 2 fixes are configuration (not patches) fixes applied from the Games DB (automatic game fixes). -// - The 1 Patch is one uncommented pnach-style patch line from the GamesDB (automatic game fixes). -// - The 0 cheats - cheats are enabled but nothing found/loaded from the "cheats" folder. -// - The 6 widescreen patches are 6 pnach-style patch lines loaded either from cheats_ws folder or from cheats_ws.zip -#include "common/Pcsx2Defs.h" -#include "SysForwardDefs.h" +#include "Config.h" + #include #include +#include -struct IConsoleWriter; - -enum patch_cpu_type { - NO_CPU, - CPU_EE, - CPU_IOP -}; - -enum patch_data_type { - NO_TYPE, - BYTE_T, - SHORT_T, - WORD_T, - DOUBLE_T, - EXTENDED_T, - SHORT_BE_T, - WORD_BE_T, - DOUBLE_BE_T -}; - -// "place" is the first number at a pnach line (patch=,...), e.g.: -// - patch=1,EE,001110e0,word,00000000 <-- place is 1 -// - patch=0,EE,0010BC88,word,48468800 <-- place is 0 -// In PCSX2 it indicates how/when/where the patch line should be applied. If -// place is not one of the supported values then the patch line is never applied. -// PCSX2 currently supports the following values: -// 0 - apply the patch line once on game boot/startup -// 1 - apply the patch line continuously (technically - on every vsync) -// 2 - effect of 0 and 1 combined, see below -// Note: -// - while it may seem that a value of 1 does the same as 0, but also later -// continues to apply the patch on every vsync - it's not. -// The current (and past) behavior is that these patches are applied at different -// places at the code, and it's possible, depending on circumstances, that 0 patches -// will get applied before the first vsync and therefore earlier than 1 patches. -// - There's no "place" value which indicates to apply both once on startup -// and then also continuously, however such behavior can be achieved by -// duplicating the line where one has a 0 place and the other has a 1 place. -enum patch_place_type { - PPT_ONCE_ON_LOAD = 0, - PPT_CONTINUOUSLY = 1, - PPT_COMBINED_0_1 = 2, - - _PPT_END_MARKER -}; - -typedef void PATCHTABLEFUNC(const std::string_view& text1, const std::string_view& text2); - -struct IniPatch +namespace Patch { - int enabled; - patch_data_type type; - patch_cpu_type cpu; - int placetopatch; - u32 addr; - u64 data; -}; + // "place" is the first number at a pnach line (patch=,...), e.g.: + // - patch=1,EE,001110e0,word,00000000 <-- place is 1 + // - patch=0,EE,0010BC88,word,48468800 <-- place is 0 + // In PCSX2 it indicates how/when/where the patch line should be applied. If + // place is not one of the supported values then the patch line is never applied. + // PCSX2 currently supports the following values: + // 0 - apply the patch line once on game boot/startup + // 1 - apply the patch line continuously (technically - on every vsync) + // 2 - effect of 0 and 1 combined, see below + // Note: + // - while it may seem that a value of 1 does the same as 0, but also later + // continues to apply the patch on every vsync - it's not. + // The current (and past) behavior is that these patches are applied at different + // places at the code, and it's possible, depending on circumstances, that 0 patches + // will get applied before the first vsync and therefore earlier than 1 patches. + // - There's no "place" value which indicates to apply both once on startup + // and then also continuously, however such behavior can be achieved by + // duplicating the line where one has a 0 place and the other has a 1 place. + enum patch_place_type + { + PPT_ONCE_ON_LOAD = 0, + PPT_CONTINUOUSLY = 1, + PPT_COMBINED_0_1 = 2, -struct DynamicPatchEntry -{ - u32 offset; - u32 value; -}; + PPT_END_MARKER + }; -struct DynamicPatch -{ - std::vector pattern; - std::vector replacement; -}; + struct PatchInfo + { + std::string name; + std::string description; + std::string author; -namespace PatchFunc -{ - PATCHTABLEFUNC author; - PATCHTABLEFUNC comment; - PATCHTABLEFUNC gametitle; - PATCHTABLEFUNC patch; -} + std::string_view GetNamePart() const; + std::string_view GetNameParentPart() const; + }; -// The following LoadPatchesFrom* functions: -// - do not reset/unload previously loaded patches (use ForgetLoadedPatches() for that) -// - do not actually patch the emulation memory (that happens at ApplyLoadedPatches(...) ) -extern int LoadPatchesFromString(const std::string& patches); -extern int LoadPatchesFromDir(const std::string& crc, const std::string& folder, const char* friendly_name, bool show_error_when_missing); -extern int LoadPatchesFromZip(const std::string& crc, const u8* zip_data, size_t zip_data_size); + using PatchInfoList = std::vector; -// Functions for Dynamic EE patching. -extern void LoadDynamicPatches(const std::vector& patches); -extern void ApplyDynamicPatches(u32 pc); + struct DynamicPatchEntry + { + u32 offset; + u32 value; + }; -// Patches the emulation memory by applying all the loaded patches with a specific place value. -// Note: unless you know better, there's no need to check whether or not different patch sources -// are enabled (e.g. ws patches, auto game fixes, etc) before calling ApplyLoadedPatches, -// because on boot or on any configuration change --> all the loaded patches are invalidated, -// 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); + struct DynamicPatch + { + std::vector pattern; + std::vector replacement; + }; -// Empties the patches store ("unload" the patches) but doesn't touch the emulation memory. -// Following ApplyLoadedPatches calls will do nothing until some LoadPatchesFrom* are invoked. -extern void ForgetLoadedPatches(); + // Config sections/keys to use to enable patches. + extern const char* PATCHES_CONFIG_SECTION; + extern const char* CHEATS_CONFIG_SECTION; + extern const char* PATCH_ENABLE_CONFIG_KEY; -extern const IConsoleWriter *PatchesCon; + extern PatchInfoList GetPatchInfo(const std::string& serial, u32 crc, bool cheats, u32* num_unlabelled_patches); -// The following prototypes seem unused in PCSX2, but maybe part of the cheats browser? -// regardless, they don't seem to have an implementation anywhere. -// extern int AddPatch(int Mode, int Place, int Address, int Size, u64 data); -// extern void ResetPatch(void); + /// Reloads cheats/patches. If verbose is set, the number of patches loaded will be shown in the OSD. + extern void ReloadPatches(std::string serial, u32 crc, bool force_reload_files, bool reload_enabled_list, bool verbose); + extern void ReloadPatches(bool force_reload_files, bool reload_enabled_list, bool verbose); + + extern void UpdateActivePatches(bool reload_enabled_list, bool verbose, bool verbose_if_changed); + extern void ApplyPatchSettingOverrides(); + extern bool ReloadPatchAffectingOptions(); + extern void UnloadPatches(); + + // Functions for Dynamic EE patching. + extern void LoadDynamicPatches(const std::vector& patches); + extern void ApplyDynamicPatches(u32 pc); + + // Patches the emulation memory by applying all the loaded patches with a specific place value. + // Note: unless you know better, there's no need to check whether or not different patch sources + // are enabled (e.g. ws patches, auto game fixes, etc) before calling ApplyLoadedPatches, + // because on boot or on any configuration change --> all the loaded patches are invalidated, + // 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); +} // namespace Patch \ No newline at end of file diff --git a/pcsx2/Patch_Memory.cpp b/pcsx2/Patch_Memory.cpp deleted file mode 100644 index ae32c4fec8..0000000000 --- a/pcsx2/Patch_Memory.cpp +++ /dev/null @@ -1,609 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2022 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#include "PrecompiledHeader.h" - -#include "common/ByteSwap.h" - -#define _PC_ // disables MIPS opcode macros. - -#include "Common.h" -#include "Patch.h" -#include "IopMem.h" - -u32 SkipCount = 0, IterationCount = 0; -u32 IterationIncrement = 0, ValueIncrement = 0; -u32 PrevCheatType = 0, PrevCheatAddr = 0, LastType = 0; - -void writeCheat() -{ - switch (LastType) - { - case 0x0: - memWrite8(PrevCheatAddr, IterationIncrement & 0xFF); - break; - case 0x1: - memWrite16(PrevCheatAddr, IterationIncrement & 0xFFFF); - break; - case 0x2: - memWrite32(PrevCheatAddr, IterationIncrement); - break; - default: - break; - } -} - -void handle_extended_t(IniPatch *p) -{ - if (SkipCount > 0) - { - SkipCount--; - } - else switch (PrevCheatType) - { - case 0x3040: // vvvvvvvv 00000000 Inc - { - u32 mem = memRead32(PrevCheatAddr); - memWrite32(PrevCheatAddr, mem + (p->addr)); - PrevCheatType = 0; - break; - } - - case 0x3050: // vvvvvvvv 00000000 Dec - { - u32 mem = memRead32(PrevCheatAddr); - memWrite32(PrevCheatAddr, mem - (p->addr)); - PrevCheatType = 0; - break; - } - - case 0x4000: // vvvvvvvv iiiiiiii - for (u32 i = 0; i < IterationCount; i++) - { - memWrite32((u32)(PrevCheatAddr + (i * IterationIncrement)), (u32)(p->addr + ((u32)p->data * i))); - } - PrevCheatType = 0; - break; - - case 0x5000: // bbbbbbbb 00000000 - for (u32 i = 0; i < IterationCount; i++) - { - u8 mem = memRead8(PrevCheatAddr + i); - memWrite8((p->addr + i) & 0x0FFFFFFF, mem); - } - PrevCheatType = 0; - break; - - case 0x6000: // 000Xnnnn iiiiiiii - { - // Get Number of pointers - if (((u32)p->addr & 0x0000FFFF) == 0) - IterationCount = 1; - else - IterationCount = (u32)p->addr & 0x0000FFFF; - - // Read first pointer - LastType = ((u32)p->addr & 0x000F0000) >> 16; - u32 mem = memRead32(PrevCheatAddr); - - PrevCheatAddr = mem + (u32)p->data; - IterationCount--; - - // Check if needed to read another pointer - if (IterationCount == 0) - { - PrevCheatType = 0; - if (((mem & 0x0FFFFFFF) & 0x3FFFFFFC) != 0) writeCheat(); - } - else - { - if (((mem & 0x0FFFFFFF) & 0x3FFFFFFC) == 0) - PrevCheatType = 0; - else - PrevCheatType = 0x6001; - } - } - break; - - case 0x6001: // 000Xnnnn iiiiiiii - { - // Read first pointer - u32 mem = memRead32(PrevCheatAddr & 0x0FFFFFFF); - - PrevCheatAddr = mem + (u32)p->addr; - IterationCount--; - - // Check if needed to read another pointer - if (IterationCount == 0) - { - PrevCheatType = 0; - if (((mem & 0x0FFFFFFF) & 0x3FFFFFFC) != 0) writeCheat(); - } - else - { - mem = memRead32(PrevCheatAddr); - - PrevCheatAddr = mem + (u32)p->data; - IterationCount--; - if (IterationCount == 0) - { - PrevCheatType = 0; - if (((mem & 0x0FFFFFFF) & 0x3FFFFFFC) != 0) writeCheat(); - } - } - } - break; - - default: - if ((p->addr & 0xF0000000) == 0x00000000) // 0aaaaaaa 0000000vv - { - memWrite8(p->addr & 0x0FFFFFFF, (u8)p->data & 0x000000FF); - PrevCheatType = 0; - } - else if ((p->addr & 0xF0000000) == 0x10000000) // 1aaaaaaa 0000vvvv - { - memWrite16(p->addr & 0x0FFFFFFF, (u16)p->data & 0x0000FFFF); - PrevCheatType = 0; - } - else if ((p->addr & 0xF0000000) == 0x20000000) // 2aaaaaaa vvvvvvvv - { - memWrite32(p->addr & 0x0FFFFFFF, (u32)p->data); - PrevCheatType = 0; - } - else if ((p->addr & 0xFFFF0000) == 0x30000000) // 300000vv 0aaaaaaa Inc - { - u8 mem = memRead8((u32)p->data); - memWrite8((u32)p->data, mem + (p->addr & 0x000000FF)); - PrevCheatType = 0; - } - else if ((p->addr & 0xFFFF0000) == 0x30100000) // 301000vv 0aaaaaaa Dec - { - u8 mem = memRead8((u32)p->data); - memWrite8((u32)p->data, mem - (p->addr & 0x000000FF)); - PrevCheatType = 0; - } - else if ((p->addr & 0xFFFF0000) == 0x30200000) // 3020vvvv 0aaaaaaa Inc - { - u16 mem = memRead16((u32)p->data); - memWrite16((u32)p->data, mem + (p->addr & 0x0000FFFF)); - PrevCheatType = 0; - } - else if ((p->addr & 0xFFFF0000) == 0x30300000) // 3030vvvv 0aaaaaaa Dec - { - u16 mem = memRead16((u32)p->data); - memWrite16((u32)p->data, mem - (p->addr & 0x0000FFFF)); - PrevCheatType = 0; - } - else if ((p->addr & 0xFFFF0000) == 0x30400000) // 30400000 0aaaaaaa Inc + Another line - { - PrevCheatType = 0x3040; - PrevCheatAddr = (u32)p->data; - } - else if ((p->addr & 0xFFFF0000) == 0x30500000) // 30500000 0aaaaaaa Inc + Another line - { - PrevCheatType = 0x3050; - PrevCheatAddr = (u32)p->data; - } - else if ((p->addr & 0xF0000000) == 0x40000000) // 4aaaaaaa nnnnssss + Another line - { - IterationCount = ((u32)p->data & 0xFFFF0000) >> 16; - IterationIncrement = ((u32)p->data & 0x0000FFFF) * 4; - PrevCheatAddr = (u32)p->addr & 0x0FFFFFFF; - PrevCheatType = 0x4000; - } - else if ((p->addr & 0xF0000000) == 0x50000000) // 5sssssss nnnnnnnn + Another line - { - PrevCheatAddr = (u32)p->addr & 0x0FFFFFFF; - IterationCount = ((u32)p->data); - PrevCheatType = 0x5000; - } - else if ((p->addr & 0xF0000000) == 0x60000000) // 6aaaaaaa 000000vv + Another line/s - { - PrevCheatAddr = (u32)p->addr & 0x0FFFFFFF; - IterationIncrement = ((u32)p->data); - IterationCount = 0; - PrevCheatType = 0x6000; - } - else if ((p->addr & 0xF0000000) == 0x70000000) - { - if ((p->data & 0x00F00000) == 0x00000000) // 7aaaaaaa 000000vv - { - u8 mem = memRead8((u32)p->addr & 0x0FFFFFFF); - memWrite8((u32)p->addr & 0x0FFFFFFF, (u8)(mem | (p->data & 0x000000FF))); - } - else if ((p->data & 0x00F00000) == 0x00100000) // 7aaaaaaa 0010vvvv - { - u16 mem = memRead16((u32)p->addr & 0x0FFFFFFF); - memWrite16((u32)p->addr & 0x0FFFFFFF, (u16)(mem | (p->data & 0x0000FFFF))); - } - else if ((p->data & 0x00F00000) == 0x00200000) // 7aaaaaaa 002000vv - { - u8 mem = memRead8((u32)p->addr & 0x0FFFFFFF); - memWrite8((u32)p->addr & 0x0FFFFFFF, (u8)(mem & (p->data & 0x000000FF))); - } - else if ((p->data & 0x00F00000) == 0x00300000) // 7aaaaaaa 0030vvvv - { - u16 mem = memRead16((u32)p->addr & 0x0FFFFFFF); - memWrite16((u32)p->addr & 0x0FFFFFFF, (u16)(mem & (p->data & 0x0000FFFF))); - } - else if ((p->data & 0x00F00000) == 0x00400000) // 7aaaaaaa 004000vv - { - u8 mem = memRead8((u32)p->addr & 0x0FFFFFFF); - memWrite8((u32)p->addr & 0x0FFFFFFF, (u8)(mem ^ (p->data & 0x000000FF))); - } - else if ((p->data & 0x00F00000) == 0x00500000) // 7aaaaaaa 0050vvvv - { - u16 mem = memRead16((u32)p->addr & 0x0FFFFFFF); - memWrite16((u32)p->addr & 0x0FFFFFFF, (u16)(mem ^ (p->data & 0x0000FFFF))); - } - } - else if ((p->addr & 0xF0000000) == 0xD0000000 || (p->addr & 0xF0000000) == 0xE0000000) - { - u32 addr = (u32)p->addr; - u32 data = (u32)p->data; - - // Since D-codes now have the additional functionality present in PS2rd which - // incorporates E-code-like functionality by making use of the unused bits in - // D-codes, the E-codes are now just converted to D-codes to reduce bloat. - - if ((addr & 0xF0000000) == 0xE0000000) - { - // Ezyyvvvv taaaaaaa -> Daaaaaaa yytzvvvv - addr = 0xD0000000 | ((u32)p->data & 0x0FFFFFFF); - data = 0x00000000 | ((u32)p->addr & 0x0000FFFF); - data = data | ((u32)p->addr & 0x00FF0000) << 8; - data = data | ((u32)p->addr & 0x0F000000) >> 8; - data = data | ((u32)p->data & 0xF0000000) >> 8; - } - - const u8 type = (data & 0x000F0000) >> 16; - const u8 cond = (data & 0x00F00000) >> 20; - - if (cond == 0) // Daaaaaaa yy0zvvvv - { - if (type == 0) // Daaaaaaa yy00vvvv - { - u16 mem = memRead16(addr & 0x0FFFFFFF); - if (mem != (data & 0x0000FFFF)) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - else if (type == 1) // Daaaaaaa yy0100vv - { - u8 mem = memRead8(addr & 0x0FFFFFFF); - if (mem != (data & 0x000000FF)) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - } - else if (cond == 1) // Daaaaaaa yy1zvvvv - { - if (type == 0) // Daaaaaaa yy10vvvv - { - u16 mem = memRead16(addr & 0x0FFFFFFF); - if (mem == (data & 0x0000FFFF)) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - else if (type == 1) // Daaaaaaa yy1100vv - { - u8 mem = memRead8(addr & 0x0FFFFFFF); - if (mem == (data & 0x000000FF)) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - } - else if (cond == 2) // Daaaaaaa yy2zvvvv - { - if (type == 0) // Daaaaaaa yy20vvvv - { - u16 mem = memRead16(addr & 0x0FFFFFFF); - if (mem >= (data & 0x0000FFFF)) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - else if (type == 1) // Daaaaaaa yy2100vv - { - u8 mem = memRead8(addr & 0x0FFFFFFF); - if (mem >= (data & 0x000000FF)) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - } - else if (cond == 3) // Daaaaaaa yy3zvvvv - { - if (type == 0) // Daaaaaaa yy30vvvv - { - u16 mem = memRead16(addr & 0x0FFFFFFF); - if (mem <= (data & 0x0000FFFF)) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - else if (type == 1) // Daaaaaaa yy3100vv - { - u8 mem = memRead8(addr & 0x0FFFFFFF); - if (mem <= (data & 0x000000FF)) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - } - else if (cond == 4) // Daaaaaaa yy4zvvvv - { - if (type == 0) // Daaaaaaa yy40vvvv - { - u16 mem = memRead16(addr & 0x0FFFFFFF); - if (mem & (data & 0x0000FFFF)) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - else if (type == 1) // Daaaaaaa yy4100vv - { - u8 mem = memRead8(addr & 0x0FFFFFFF); - if (mem & (data & 0x000000FF)) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - } - else if (cond == 5) // Daaaaaaa yy5zvvvv - { - if (type == 0) // Daaaaaaa yy50vvvv - { - u16 mem = memRead16(addr & 0x0FFFFFFF); - if (!(mem & (data & 0x0000FFFF))) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - else if (type == 1) // Daaaaaaa yy5100vv - { - u8 mem = memRead8(addr & 0x0FFFFFFF); - if (!(mem & (data & 0x000000FF))) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - } - else if (cond == 6) // Daaaaaaa yy6zvvvv - { - if (type == 0) // Daaaaaaa yy60vvvv - { - u16 mem = memRead16(addr & 0x0FFFFFFF); - if (mem | (data & 0x0000FFFF)) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - else if (type == 1) // Daaaaaaa yy6100vv - { - u8 mem = memRead8(addr & 0x0FFFFFFF); - if (mem | (data & 0x000000FF)) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - } - else if (cond == 7) // Daaaaaaa yy7zvvvv - { - if (type == 0) // Daaaaaaa yy70vvvv - { - u16 mem = memRead16(addr & 0x0FFFFFFF); - if (!(mem | (data & 0x0000FFFF))) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - else if (type == 1) // Daaaaaaa yy7100vv - { - u8 mem = memRead8(addr & 0x0FFFFFFF); - if (!(mem | (data & 0x000000FF))) - { - SkipCount = (data & 0xFF000000) >> 24; - if (!SkipCount) - { - SkipCount = 1; - } - } - PrevCheatType = 0; - } - } - } - } -} - -// Only used from Patch.cpp and we don't export this in any h file. -// Patch.cpp itself declares this prototype, so make sure to keep in sync. -void _ApplyPatch(IniPatch *p) -{ - u64 ledata = 0; - - if (p->enabled == 0) return; - - switch (p->cpu) - { - case CPU_EE: - switch (p->type) - { - case BYTE_T: - if (memRead8(p->addr) != (u8)p->data) - memWrite8(p->addr, (u8)p->data); - break; - - case SHORT_T: - if (memRead16(p->addr) != (u16)p->data) - memWrite16(p->addr, (u16)p->data); - break; - - case WORD_T: - if (memRead32(p->addr) != (u32)p->data) - memWrite32(p->addr, (u32)p->data); - break; - - case DOUBLE_T: - if (memRead64(p->addr) != (u64)p->data) - memWrite64(p->addr, (u64)p->data); - break; - - case EXTENDED_T: - handle_extended_t(p); - break; - - case SHORT_BE_T: - ledata = ByteSwap(static_cast(p->data)); - if (memRead16(p->addr) != (u16)ledata) - memWrite16(p->addr, (u16)ledata); - break; - - case WORD_BE_T: - ledata = ByteSwap(static_cast(p->data)); - if (memRead32(p->addr) != (u32)ledata) - memWrite32(p->addr, (u32)ledata); - break; - - case DOUBLE_BE_T: - ledata = ByteSwap(p->data); - if (memRead64(p->addr) != (u64)ledata) - memWrite64(p->addr, (u64)ledata); - break; - - default: - break; - } - break; - - case CPU_IOP: - switch (p->type) - { - case BYTE_T: - if (iopMemRead8(p->addr) != (u8)p->data) - iopMemWrite8(p->addr, (u8)p->data); - break; - case SHORT_T: - if (iopMemRead16(p->addr) != (u16)p->data) - iopMemWrite16(p->addr, (u16)p->data); - break; - case WORD_T: - if (iopMemRead32(p->addr) != (u32)p->data) - iopMemWrite32(p->addr, (u32)p->data); - break; - default: - break; - } - break; - - default: - break; - } -} - -void _ApplyDynaPatch(const DynamicPatch& patch, u32 address) -{ - for (const auto& pattern : patch.pattern) - { - if (*static_cast(PSM(address + pattern.offset)) != pattern.value) - return; - } - - PatchesCon->WriteLn("Applying Dynamic Patch to address 0x%08X", address); - // If everything passes, apply the patch. - for (const auto& replacement : patch.replacement) - { - memWrite32(address + replacement.offset, replacement.value); - } -} diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index f3782b2dbb..b9efdde5b1 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -100,8 +100,7 @@ namespace EmuFolders std::string Langs; std::string Logs; std::string Cheats; - std::string CheatsWS; - std::string CheatsNI; + std::string Patches; std::string Resources; std::string Cache; std::string Covers; @@ -1613,8 +1612,7 @@ void EmuFolders::SetDefaults(SettingsInterface& si) si.SetStringValue("Folders", "MemoryCards", "memcards"); si.SetStringValue("Folders", "Logs", "logs"); si.SetStringValue("Folders", "Cheats", "cheats"); - si.SetStringValue("Folders", "CheatsWS", "cheats_ws"); - si.SetStringValue("Folders", "CheatsNI", "cheats_ni"); + si.SetStringValue("Folders", "Patches", "patches"); si.SetStringValue("Folders", "Cache", "cache"); si.SetStringValue("Folders", "Textures", "textures"); si.SetStringValue("Folders", "InputProfiles", "inputprofiles"); @@ -1637,8 +1635,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si) MemoryCards = LoadPathFromSettings(si, DataRoot, "MemoryCards", "memcards"); Logs = LoadPathFromSettings(si, DataRoot, "Logs", "logs"); Cheats = LoadPathFromSettings(si, DataRoot, "Cheats", "cheats"); - CheatsWS = LoadPathFromSettings(si, DataRoot, "CheatsWS", "cheats_ws"); - CheatsNI = LoadPathFromSettings(si, DataRoot, "CheatsNI", "cheats_ni"); + Patches = LoadPathFromSettings(si, DataRoot, "Patches", "patches"); Covers = LoadPathFromSettings(si, DataRoot, "Covers", "covers"); GameSettings = LoadPathFromSettings(si, DataRoot, "GameSettings", "gamesettings"); Cache = LoadPathFromSettings(si, DataRoot, "Cache", "cache"); @@ -1652,8 +1649,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si) Console.WriteLn("MemoryCards Directory: %s", MemoryCards.c_str()); Console.WriteLn("Logs Directory: %s", Logs.c_str()); Console.WriteLn("Cheats Directory: %s", Cheats.c_str()); - Console.WriteLn("CheatsWS Directory: %s", CheatsWS.c_str()); - Console.WriteLn("CheatsNI Directory: %s", CheatsNI.c_str()); + Console.WriteLn("Patches Directory: %s", Patches.c_str()); Console.WriteLn("Covers Directory: %s", Covers.c_str()); Console.WriteLn("Game Settings Directory: %s", GameSettings.c_str()); Console.WriteLn("Cache Directory: %s", Cache.c_str()); @@ -1671,8 +1667,7 @@ bool EmuFolders::EnsureFoldersExist() result = FileSystem::CreateDirectoryPath(MemoryCards.c_str(), false) && result; result = FileSystem::CreateDirectoryPath(Logs.c_str(), false) && result; result = FileSystem::CreateDirectoryPath(Cheats.c_str(), false) && result; - result = FileSystem::CreateDirectoryPath(CheatsWS.c_str(), false) && result; - result = FileSystem::CreateDirectoryPath(CheatsNI.c_str(), false) && result; + result = FileSystem::CreateDirectoryPath(Patches.c_str(), false) && result; result = FileSystem::CreateDirectoryPath(Covers.c_str(), false) && result; result = FileSystem::CreateDirectoryPath(GameSettings.c_str(), false) && result; result = FileSystem::CreateDirectoryPath(Cache.c_str(), false) && result; diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index f7246c41b7..a5b9689c8c 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -15,57 +15,56 @@ #include "PrecompiledHeader.h" -#include "VMManager.h" #include "Achievements.h" -#include "Counters.h" #include "CDVD/CDVD.h" +#include "Counters.h" #include "DEV9/DEV9.h" +#include "DebugTools/MIPSAnalyst.h" +#include "DebugTools/SymbolMap.h" #include "Elfheader.h" #include "FW.h" -#include "GameDatabase.h" -#include "GameList.h" #include "GS.h" #include "GS/Renderers/HW/GSTextureReplacements.h" #include "GSDumpReplayer.h" +#include "GameDatabase.h" +#include "GameList.h" #include "Host.h" -#include "ImGui/FullscreenUI.h" #include "INISettingsInterface.h" +#include "ImGui/FullscreenUI.h" #include "Input/InputManager.h" #include "IopBios.h" #include "LogSink.h" #include "MTVU.h" #include "MemoryCardFile.h" -#include "Patch.h" -#include "PerformanceMetrics.h" -#include "PINE.h" -#include "R5900.h" -#include "SPU2/spu2.h" -#include "DEV9/DEV9.h" -#include "USB/USB.h" #include "PAD/Host/PAD.h" #include "PCSX2Base.h" -#include "Sio.h" -#include "ps2/BiosTools.h" -#include "Recording/InputRecordingControls.h" -#include "DebugTools/MIPSAnalyst.h" -#include "DebugTools/SymbolMap.h" +#include "PINE.h" +#include "Patch.h" +#include "PerformanceMetrics.h" +#include "R5900.h" #include "Recording/InputRecording.h" +#include "Recording/InputRecordingControls.h" +#include "SPU2/spu2.h" +#include "Sio.h" +#include "USB/USB.h" +#include "VMManager.h" +#include "ps2/BiosTools.h" #include "common/Console.h" #include "common/FileSystem.h" #include "common/ScopedGuard.h" -#include "common/StringUtil.h" #include "common/SettingsWrapper.h" -#include "common/Timer.h" +#include "common/StringUtil.h" #include "common/Threading.h" +#include "common/Timer.h" #include "common/emitter/tools.h" #include "IconsFontAwesome5.h" #include "fmt/core.h" #include -#include #include +#include #ifdef _M_X86 #include "common/emitter/x86_intrin.h" @@ -104,19 +103,17 @@ namespace VMManager static bool AutoDetectSource(const std::string& filename); static bool ApplyBootParameters(VMBootParameters params, std::string* state_to_load); static bool CheckBIOSAvailability(); - static void LoadPatches(const std::string& serial, u32 crc, - bool show_messages, bool show_messages_when_disabled); static void UpdateRunningGame(bool resetting, bool game_starting, bool swapping); static std::string GetCurrentSaveStateFileName(s32 slot); static bool DoLoadState(const char* filename); static bool DoSaveState(const char* filename, s32 slot_for_message, bool zip_on_thread, bool backup_old_state); static void ZipSaveState(std::unique_ptr elist, - std::unique_ptr screenshot, std::string osd_key, - const char* filename, s32 slot_for_message); + std::unique_ptr screenshot, std::string osd_key, const char* filename, + s32 slot_for_message); static void ZipSaveStateOnThread(std::unique_ptr elist, - std::unique_ptr screenshot, std::string osd_key, - std::string filename, s32 slot_for_message); + std::unique_ptr screenshot, std::string osd_key, std::string filename, + s32 slot_for_message); static void UpdateInhibitScreensaver(bool allow); static void SaveSessionTime(); @@ -155,12 +152,6 @@ static std::string s_game_name; static std::string s_elf_override; static std::string s_input_profile_name; static u32 s_active_game_fixes = 0; -static std::vector s_widescreen_cheats_data; -static bool s_widescreen_cheats_loaded = false; -static std::vector s_no_interlacing_cheats_data; -static bool s_no_interlacing_cheats_loaded = false; -static s32 s_active_widescreen_patches = 0; -static u32 s_active_no_interlacing_patches = 0; static u32 s_frame_advance_count = 0; static u32 s_mxcsr_saved; static bool s_gs_open_on_initialize = false; @@ -178,8 +169,7 @@ static bool s_discord_presence_active = false; bool VMManager::PerformEarlyHardwareChecks(const char** error) { -#define COMMON_DOWNLOAD_MESSAGE \ - "PCSX2 builds can be downloaded from https://pcsx2.net/downloads/" +#define COMMON_DOWNLOAD_MESSAGE "PCSX2 builds can be downloaded from https://pcsx2.net/downloads/" #if defined(_M_X86) // On Windows, this gets called as a global object constructor, before any of our objects are constructed. @@ -189,16 +179,20 @@ bool VMManager::PerformEarlyHardwareChecks(const char** error) if (!temp_x86_caps.hasStreamingSIMD4Extensions) { - *error = "PCSX2 requires the Streaming SIMD 4.1 Extensions instruction set, which your CPU does not support.\n\n" - "SSE4.1 is now a minimum requirement for PCSX2. You should either upgrade your CPU, or use an older build such as 1.6.0.\n\n" COMMON_DOWNLOAD_MESSAGE; + *error = + "PCSX2 requires the Streaming SIMD 4.1 Extensions instruction set, which your CPU does not support.\n\n" + "SSE4.1 is now a minimum requirement for PCSX2. You should either upgrade your CPU, or use an older build " + "such as 1.6.0.\n\n" COMMON_DOWNLOAD_MESSAGE; return false; } #if _M_SSE >= 0x0501 if (!temp_x86_caps.hasAVX || !temp_x86_caps.hasAVX2) { - *error = "This build of PCSX2 requires the Advanced Vector Extensions 2 instruction set, which your CPU does not support.\n\n" - "You should download and run the SSE4.1 build of PCSX2 instead, or upgrade to a CPU that supports AVX2 to use this build.\n\n" COMMON_DOWNLOAD_MESSAGE; + *error = "This build of PCSX2 requires the Advanced Vector Extensions 2 instruction set, which your CPU does " + "not support.\n\n" + "You should download and run the SSE4.1 build of PCSX2 instead, or upgrade to a CPU that supports " + "AVX2 to use this build.\n\n" COMMON_DOWNLOAD_MESSAGE; return false; } #endif @@ -345,11 +339,6 @@ void VMManager::Internal::CPUThreadShutdown() InputManager::CloseSources(); WaitForSaveStateFlush(); - std::vector().swap(s_widescreen_cheats_data); - s_widescreen_cheats_loaded = false; - std::vector().swap(s_no_interlacing_cheats_data); - s_no_interlacing_cheats_loaded = false; - s_cpu_provider_pack.reset(); s_vm_memory.reset(); @@ -395,7 +384,8 @@ void VMManager::Internal::LoadStartupSettings() #endif } -void VMManager::SetDefaultSettings(SettingsInterface& si, bool folders, bool core, bool controllers, bool hotkeys, bool ui) +void VMManager::SetDefaultSettings( + SettingsInterface& si, bool folders, bool core, bool controllers, bool hotkeys, bool ui) { if (si.GetUIntValue("UI", "SettingsVersion", 0u) != SETTINGS_VERSION) si.SetUIntValue("UI", "SettingsVersion", SETTINGS_VERSION); @@ -433,6 +423,7 @@ void VMManager::LoadSettings() InputManager::ReloadSources(*si, lock); InputManager::ReloadBindings(*si, *Host::GetSettingsInterfaceForBindings()); LogSink::UpdateLogging(*si); + Patch::ApplyPatchSettingOverrides(); // Achievements hardcore mode disallows setting some configuration options. EnforceAchievementsChallengeModeSettings(); @@ -441,20 +432,6 @@ void VMManager::LoadSettings() EmuConfig.GS.MaskUserHacks(); EmuConfig.GS.MaskUpscalingHacks(); - // Disable interlacing if we have no-interlacing patches active. - if (s_active_no_interlacing_patches > 0 && EmuConfig.GS.InterlaceMode == GSInterlaceMode::Automatic) - EmuConfig.GS.InterlaceMode = GSInterlaceMode::Off; - - // Switch to 16:9 if widescreen patches are enabled, and AR is auto. - if (s_active_widescreen_patches > 0 && EmuConfig.GS.AspectRatio == AspectRatioType::RAuto4_3_3_2) - { - // Don't change when reloading settings in the middle of a FMV with switch. - if (EmuConfig.CurrentAspectRatio == EmuConfig.GS.AspectRatio) - EmuConfig.CurrentAspectRatio = AspectRatioType::R16_9; - - EmuConfig.GS.AspectRatio = AspectRatioType::R16_9; - } - // Force MTVU off when playing back GS dumps, it doesn't get used. if (GSDumpReplayer::IsReplayingDump()) EmuConfig.Speedhacks.vuThread = false; @@ -517,8 +494,7 @@ std::string VMManager::GetInputProfilePath(const std::string_view& name) void VMManager::Internal::UpdateEmuFolders() { const std::string old_cheats_directory(EmuFolders::Cheats); - const std::string old_cheats_ws_directory(EmuFolders::CheatsWS); - const std::string old_cheats_ni_directory(EmuFolders::CheatsNI); + const std::string old_patches_directory(EmuFolders::Patches); const std::string old_memcards_directory(EmuFolders::MemoryCards); const std::string old_textures_directory(EmuFolders::Textures); const std::string old_videos_directory(EmuFolders::Videos); @@ -529,11 +505,8 @@ void VMManager::Internal::UpdateEmuFolders() if (VMManager::HasValidVM()) { - if (EmuFolders::Cheats != old_cheats_directory || EmuFolders::CheatsWS != old_cheats_ws_directory || - EmuFolders::CheatsNI != old_cheats_ni_directory) - { - VMManager::ReloadPatches(true, true); - } + if (EmuFolders::Cheats != old_cheats_directory || EmuFolders::Patches != old_patches_directory) + Patch::ReloadPatches(s_game_serial, s_game_crc, true, false, true); if (EmuFolders::MemoryCards != old_memcards_directory) { @@ -679,7 +652,8 @@ bool VMManager::UpdateGameSettingsLayer() } } - Host::Internal::SetInputSettingsLayer(input_interface ? input_interface.get() : Host::Internal::GetBaseSettingsLayer()); + Host::Internal::SetInputSettingsLayer( + input_interface ? input_interface.get() : Host::Internal::GetBaseSettingsLayer()); } else { @@ -692,144 +666,6 @@ bool VMManager::UpdateGameSettingsLayer() return true; } -void VMManager::LoadPatches(const std::string& serial, u32 crc, bool show_messages, bool show_messages_when_disabled) -{ - const std::string crc_string(fmt::format("{:08X}", crc)); - s_patches_crc = crc; - s_active_widescreen_patches = 0; - s_active_no_interlacing_patches = 0; - ForgetLoadedPatches(); - - std::string message; - - int patch_count = 0; - if (EmuConfig.EnablePatches) - { - const GameDatabaseSchema::GameEntry* game = GameDatabase::findGame(serial); - if (game) - { - const std::string* patches = game->findPatch(crc); - if (patches && (patch_count = LoadPatchesFromString(*patches)) > 0) - { - PatchesCon->WriteLn(Color_Green, "(GameDB) Patches Loaded: %d", patch_count); - fmt::format_to(std::back_inserter(message), "{} game patches", patch_count); - } - - LoadDynamicPatches(game->dynaPatches); - } - } - - // regular cheat patches - int cheat_count = 0; - if (EmuConfig.EnableCheats) - { - cheat_count = LoadPatchesFromDir(crc_string, EmuFolders::Cheats, "Cheats", true); - if (cheat_count > 0) - { - PatchesCon->WriteLn(Color_Green, "Cheats Loaded: %d", cheat_count); - fmt::format_to(std::back_inserter(message), "{}{} cheat patches", (patch_count > 0) ? " and " : "", cheat_count); - } - } - - // wide screen patches - if (EmuConfig.EnableWideScreenPatches && crc != 0) - { - if (!Achievements::ChallengeModeActive() && (s_active_widescreen_patches = LoadPatchesFromDir(crc_string, EmuFolders::CheatsWS, "Widescreen hacks", false)) > 0) - { - Console.WriteLn(Color_Gray, "Found widescreen patches in the cheats_ws folder --> skipping cheats_ws.zip"); - } - else - { - // No ws cheat files found at the cheats_ws folder, try the ws cheats zip file. - if (!s_widescreen_cheats_loaded) - { - s_widescreen_cheats_loaded = true; - - std::optional> data = Host::ReadResourceFile("cheats_ws.zip"); - if (data.has_value()) - s_widescreen_cheats_data = std::move(data.value()); - } - - if (!s_widescreen_cheats_data.empty()) - { - s_active_widescreen_patches = LoadPatchesFromZip(crc_string, s_widescreen_cheats_data.data(), s_widescreen_cheats_data.size()); - PatchesCon->WriteLn(Color_Green, "(Wide Screen Cheats DB) Patches Loaded: %d", s_active_widescreen_patches); - } - } - - if (s_active_widescreen_patches > 0) - { - fmt::format_to(std::back_inserter(message), "{}{} widescreen patches", (patch_count > 0 || cheat_count > 0) ? " and " : "", s_active_widescreen_patches); - - // Switch to 16:9 if widescreen patches are enabled, and AR is auto. - if (EmuConfig.GS.AspectRatio == AspectRatioType::RAuto4_3_3_2) - { - // Don't change when reloading settings in the middle of a FMV with switch. - if (EmuConfig.CurrentAspectRatio == EmuConfig.GS.AspectRatio) - EmuConfig.CurrentAspectRatio = AspectRatioType::R16_9; - - EmuConfig.GS.AspectRatio = AspectRatioType::R16_9; - } - } - } - - // no-interlacing patches - if (EmuConfig.EnableNoInterlacingPatches && crc != 0) - { - if (!Achievements::ChallengeModeActive() && (s_active_no_interlacing_patches = LoadPatchesFromDir(crc_string, EmuFolders::CheatsNI, "No-interlacing patches", false)) > 0) - { - Console.WriteLn(Color_Gray, "Found no-interlacing patches in the cheats_ni folder --> skipping cheats_ni.zip"); - } - else - { - // No ws cheat files found at the cheats_ws folder, try the ws cheats zip file. - if (!s_no_interlacing_cheats_loaded) - { - s_no_interlacing_cheats_loaded = true; - - std::optional> data = Host::ReadResourceFile("cheats_ni.zip"); - if (data.has_value()) - s_no_interlacing_cheats_data = std::move(data.value()); - } - - if (!s_no_interlacing_cheats_data.empty()) - { - s_active_no_interlacing_patches = LoadPatchesFromZip(crc_string, s_no_interlacing_cheats_data.data(), s_no_interlacing_cheats_data.size()); - PatchesCon->WriteLn(Color_Green, "(No-Interlacing Cheats DB) Patches Loaded: %u", s_active_no_interlacing_patches); - } - } - - if (s_active_no_interlacing_patches > 0) - { - fmt::format_to(std::back_inserter(message), "{}{} no-interlacing patches", (patch_count > 0 || cheat_count > 0 || s_active_widescreen_patches > 0) ? " and " : "", s_active_no_interlacing_patches); - - // Disable interlacing in GS if active. - if (EmuConfig.GS.InterlaceMode == GSInterlaceMode::Automatic) - { - EmuConfig.GS.InterlaceMode = GSInterlaceMode::Off; - GetMTGS().ApplySettings(); - } - } - } - else - { - s_active_no_interlacing_patches = 0; - } - - if (show_messages) - { - if (cheat_count > 0 || s_active_widescreen_patches > 0 || s_active_no_interlacing_patches > 0) - { - message += " are active."; - Host::AddIconOSDMessage("LoadPatches", ICON_FA_FILE_CODE, message, Host::OSD_INFO_DURATION); - } - else if (show_messages_when_disabled) - { - Host::AddIconOSDMessage("LoadPatches", ICON_FA_FILE_CODE, "No cheats or patches (widescreen, compatibility or others) are found / enabled.", Host::OSD_INFO_DURATION); - } - } -} - void VMManager::UpdateRunningGame(bool resetting, bool game_starting, bool swapping_disc) { // The CRC can be known before the game actually starts (at the bios), so when @@ -887,7 +723,16 @@ void VMManager::UpdateRunningGame(bool resetting, bool game_starting, bool swapp Console.WriteLn(Color_StrongGreen, fmt::format(" Serial: {}", s_game_serial)); Console.WriteLn(Color_StrongGreen, fmt::format(" CRC: {:08X}", s_game_crc)); + // When resetting, patches need to get removed here, because there's no entry point being compiled. + if (resetting) + Patch::ReloadPatches(s_game_serial, s_game_crc, false, false, false); + UpdateGameSettingsLayer(); + + // Must be done before ApplySettings(), so WS/NI configs are picked up. + // Actual patch files get loaded on the entry point compiling. + Patch::UpdateActivePatches(true, false, s_game_crc != 0); + ApplySettings(); if (!swapping_disc) @@ -897,11 +742,8 @@ void VMManager::UpdateRunningGame(bool resetting, bool game_starting, bool swapp if (game_starting || resetting) AutoEject::ClearAll(); - // Check this here, for two cases: dynarec on, and when enable cheats is set per-game. - if (s_patches_crc != s_game_crc) - ReloadPatches(game_starting, false); - - MIPSAnalyst::ScanForFunctions(R5900SymbolMap, ElfTextRange.first, ElfTextRange.first + ElfTextRange.second, true); + MIPSAnalyst::ScanForFunctions( + R5900SymbolMap, ElfTextRange.first, ElfTextRange.first + ElfTextRange.second, true); R5900SymbolMap.UpdateActiveSymbols(); R3000SymbolMap.UpdateActiveSymbols(); } @@ -920,11 +762,6 @@ void VMManager::UpdateRunningGame(bool resetting, bool game_starting, bool swapp Host::OnGameChanged(s_disc_path, s_elf_override, s_game_serial, s_game_name, s_game_crc); } -void VMManager::ReloadPatches(bool verbose, bool show_messages_when_disabled) -{ - LoadPatches(s_game_serial, s_game_crc, verbose, show_messages_when_disabled); -} - static LimiterModeType GetInitialLimiterMode() { return EmuConfig.GS.FrameLimitEnable ? LimiterModeType::Nominal : LimiterModeType::Unlimited; @@ -1068,8 +905,10 @@ bool VMManager::CheckBIOSAvailability() // TODO: When we translate core strings, translate this. const char* message = "PCSX2 requires a PS2 BIOS in order to run.\n\n" - "For legal reasons, you *must* obtain a BIOS from an actual PS2 unit that you own (borrowing doesn't count).\n\n" - "Once dumped, this BIOS image should be placed in the bios folder within the data directory (Tools Menu -> Open Data Directory).\n\n" + "For legal reasons, you *must* obtain a BIOS from an actual PS2 unit that you own (borrowing " + "doesn't count).\n\n" + "Once dumped, this BIOS image should be placed in the bios folder within the data directory " + "(Tools Menu -> Open Data Directory).\n\n" "Please consult the FAQs and Guides for further instructions."; Host::ReportErrorAsync("Startup Error", message); @@ -1168,9 +1007,7 @@ bool VMManager::Initialize(VMBootParameters boot_params) Host::ReportErrorAsync("Startup Error", "Failed to initialize USB."); return false; } - ScopedGuard close_usb = []() { - USBclose(); - }; + ScopedGuard close_usb = []() { USBclose(); }; Console.WriteLn("Opening FW..."); if (FWopen() != 0) @@ -1204,7 +1041,6 @@ bool VMManager::Initialize(VMBootParameters boot_params) SysClearExecutionCache(); memBindConditionalHandlers(); - ForgetLoadedPatches(); gsUpdateFrequency(EmuConfig); frameLimitReset(); cpuReset(); @@ -1279,8 +1115,6 @@ void VMManager::Shutdown(bool save_resume_state) Host::OnGameChanged(s_disc_path, s_elf_override, s_game_serial, s_game_name, 0); } s_active_game_fixes = 0; - s_active_widescreen_patches = 0; - s_active_no_interlacing_patches = 0; UpdateGameSettingsLayer(); @@ -1292,7 +1126,7 @@ void VMManager::Shutdown(bool save_resume_state) a64_setfpcr(s_mxcsr_saved); #endif - ForgetLoadedPatches(); + Patch::UnloadPatches(); R3000A::ioman::reset(); vtlb_Shutdown(); USBclose(); @@ -1350,8 +1184,6 @@ void VMManager::Reset() const bool game_was_started = g_GameStarted; s_active_game_fixes = 0; - s_active_widescreen_patches = 0; - s_active_no_interlacing_patches = 0; SysClearExecutionCache(); memBindConditionalHandlers(); @@ -1460,7 +1292,8 @@ bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip if (!FileSystem::RenamePath(filename, backup_filename.c_str())) { Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_EXCLAMATION_TRIANGLE, - fmt::format("Failed to back up old save state {}.", Path::GetFileName(filename)), Host::OSD_ERROR_DURATION); + fmt::format("Failed to back up old save state {}.", Path::GetFileName(filename)), + Host::OSD_ERROR_DURATION); } } @@ -1468,9 +1301,8 @@ bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip { // lock order here is important; the thread could exit before we resume here. std::unique_lock lock(s_save_state_threads_mutex); - s_save_state_threads.emplace_back(&VMManager::ZipSaveStateOnThread, - std::move(elist), std::move(screenshot), std::move(osd_key), std::string(filename), - slot_for_message); + s_save_state_threads.emplace_back(&VMManager::ZipSaveStateOnThread, std::move(elist), std::move(screenshot), + std::move(osd_key), std::string(filename), slot_for_message); } else { @@ -1482,35 +1314,36 @@ bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip } catch (Exception::BaseException& e) { - Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_EXCLAMATION_TRIANGLE, fmt::format("Failed to save save state: {}.", e.DiagMsg()), - Host::OSD_ERROR_DURATION); + Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_EXCLAMATION_TRIANGLE, + fmt::format("Failed to save save state: {}.", e.DiagMsg()), Host::OSD_ERROR_DURATION); return false; } } void VMManager::ZipSaveState(std::unique_ptr elist, - std::unique_ptr screenshot, std::string osd_key, - const char* filename, s32 slot_for_message) + std::unique_ptr screenshot, std::string osd_key, const char* filename, + s32 slot_for_message) { Common::Timer timer; if (SaveState_ZipToDisk(std::move(elist), std::move(screenshot), filename)) { if (slot_for_message >= 0 && VMManager::HasValidVM()) - Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_SAVE, fmt::format("State saved to slot {}.", slot_for_message), - Host::OSD_QUICK_DURATION); + Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_SAVE, + fmt::format("State saved to slot {}.", slot_for_message), Host::OSD_QUICK_DURATION); } else { - Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_EXCLAMATION_TRIANGLE, fmt::format("Failed to save save state to slot {}.", slot_for_message), - Host::OSD_ERROR_DURATION); + Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_EXCLAMATION_TRIANGLE, + fmt::format("Failed to save save state to slot {}.", slot_for_message), Host::OSD_ERROR_DURATION); } DevCon.WriteLn("Zipping save state to '%s' took %.2f ms", filename, timer.GetTimeMilliseconds()); } -void VMManager::ZipSaveStateOnThread(std::unique_ptr elist, std::unique_ptr screenshot, - std::string osd_key, std::string filename, s32 slot_for_message) +void VMManager::ZipSaveStateOnThread(std::unique_ptr elist, + std::unique_ptr screenshot, std::string osd_key, std::string filename, + s32 slot_for_message) { ZipSaveState(std::move(elist), std::move(screenshot), std::move(osd_key), filename.c_str(), slot_for_message); @@ -1568,8 +1401,7 @@ u32 VMManager::DeleteSaveStates(const char* game_serial, u32 game_crc, bool also bool VMManager::LoadState(const char* filename) { #ifdef ENABLE_ACHIEVEMENTS - if (Achievements::ChallengeModeActive() && - !Achievements::ConfirmChallengeModeDisable("Loading state")) + if (Achievements::ChallengeModeActive() && !Achievements::ConfirmChallengeModeDisable("Loading state")) { return false; } @@ -1588,19 +1420,20 @@ bool VMManager::LoadStateFromSlot(s32 slot) const std::string filename(GetCurrentSaveStateFileName(slot)); if (filename.empty()) { - Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_EXCLAMATION_TRIANGLE, fmt::format("There is no save state in slot {}.", slot), 5.0f); + Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_EXCLAMATION_TRIANGLE, + fmt::format("There is no save state in slot {}.", slot), 5.0f); return false; } #ifdef ENABLE_ACHIEVEMENTS - if (Achievements::ChallengeModeActive() && - !Achievements::ConfirmChallengeModeDisable("Loading state")) + if (Achievements::ChallengeModeActive() && !Achievements::ConfirmChallengeModeDisable("Loading state")) { return false; } #endif - Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_FOLDER_OPEN, fmt::format("Loading state from slot {}...", slot), Host::OSD_QUICK_DURATION); + Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_FOLDER_OPEN, + fmt::format("Loading state from slot {}...", slot), Host::OSD_QUICK_DURATION); return DoLoadState(filename.c_str()); } @@ -1616,7 +1449,8 @@ bool VMManager::SaveStateToSlot(s32 slot, bool zip_on_thread) return false; // if it takes more than a minute.. well.. wtf. - Host::AddIconOSDMessage(fmt::format("SaveStateSlot{}", slot), ICON_FA_SAVE, fmt::format("Saving state to slot {}...", slot), 60.0f); + Host::AddIconOSDMessage( + fmt::format("SaveStateSlot{}", slot), ICON_FA_SAVE, fmt::format("Saving state to slot {}...", slot), 60.0f); return DoSaveState(filename.c_str(), slot, zip_on_thread, EmuConfig.BackupSavestate); } @@ -1665,18 +1499,21 @@ bool VMManager::ChangeDisc(CDVD_SourceType source, std::string path) if (source == CDVD_SourceType::NoDisc) Host::AddIconOSDMessage("ChangeDisc", ICON_FA_COMPACT_DISC, "Disc removed.", Host::OSD_INFO_DURATION); else - Host::AddIconOSDMessage("ChangeDisc", ICON_FA_COMPACT_DISC, fmt::format("Disc changed to '{}'.", display_name), Host::OSD_INFO_DURATION); + Host::AddIconOSDMessage("ChangeDisc", ICON_FA_COMPACT_DISC, + fmt::format("Disc changed to '{}'.", display_name), Host::OSD_INFO_DURATION); } else { - Host::AddIconOSDMessage("ChangeDisc", ICON_FA_COMPACT_DISC, fmt::format("Failed to open new disc image '{}'. Reverting to old image.", display_name), + Host::AddIconOSDMessage("ChangeDisc", ICON_FA_COMPACT_DISC, + fmt::format("Failed to open new disc image '{}'. Reverting to old image.", display_name), Host::OSD_ERROR_DURATION); CDVDsys_ChangeSource(old_type); if (!old_path.empty()) CDVDsys_SetFile(old_type, std::move(old_path)); if (!DoCDVDopen()) { - Host::AddIconOSDMessage("ChangeDisc", ICON_FA_COMPACT_DISC, "Failed to switch back to old disc image. Removing disc.", Host::OSD_CRITICAL_ERROR_DURATION); + Host::AddIconOSDMessage("ChangeDisc", ICON_FA_COMPACT_DISC, + "Failed to switch back to old disc image. Removing disc.", Host::OSD_CRITICAL_ERROR_DURATION); CDVDsys_ChangeSource(CDVD_SourceType::NoDisc); DoCDVDopen(); } @@ -1698,8 +1535,7 @@ bool VMManager::IsBlockDumpFileName(const std::string_view& path) bool VMManager::IsGSDumpFileName(const std::string_view& path) { - return (StringUtil::EndsWithNoCase(path, ".gs") || - StringUtil::EndsWithNoCase(path, ".gs.xz") || + return (StringUtil::EndsWithNoCase(path, ".gs") || StringUtil::EndsWithNoCase(path, ".gs.xz") || StringUtil::EndsWithNoCase(path, ".gs.zst")); } @@ -1778,15 +1614,16 @@ void VMManager::Internal::EntryPointCompilingOnCPUThread() // until the game entry point actually runs, because that can update settings, which // can flush the JIT, etc. But we need to apply patches for games where the entry // point is in the patch (e.g. WRC 4). So. Gross, but the only way to handle it really. - LoadPatches(SysGetDiscID(), ElfCRC, true, false); - ApplyLoadedPatches(PPT_ONCE_ON_LOAD); + Patch::ReloadPatches(SysGetDiscID(), ElfCRC, false, false, false); + Patch::ApplyLoadedPatches(Patch::PPT_ONCE_ON_LOAD); } void VMManager::Internal::GameStartingOnCPUThread() { + // See note above. UpdateRunningGame(false, true, false); - ApplyLoadedPatches(PPT_ONCE_ON_LOAD); - ApplyLoadedPatches(PPT_COMBINED_0_1); + Patch::ApplyLoadedPatches(Patch::PPT_ONCE_ON_LOAD); + Patch::ApplyLoadedPatches(Patch::PPT_COMBINED_0_1); } void VMManager::Internal::SwappingGameOnCPUThread() @@ -1797,8 +1634,8 @@ void VMManager::Internal::SwappingGameOnCPUThread() void VMManager::Internal::VSyncOnCPUThread() { // TODO: Move frame limiting here to reduce CPU usage after sleeping... - ApplyLoadedPatches(PPT_CONTINUOUSLY); - ApplyLoadedPatches(PPT_COMBINED_0_1); + Patch::ApplyLoadedPatches(Patch::PPT_CONTINUOUSLY); + Patch::ApplyLoadedPatches(Patch::PPT_COMBINED_0_1); // Frame advance must be done *before* pumping messages, because otherwise // we'll immediately reduce the counter we just set. @@ -1841,10 +1678,8 @@ void VMManager::Internal::VSyncOnCPUThread() void VMManager::CheckForCPUConfigChanges(const Pcsx2Config& old_config) { - if (EmuConfig.Cpu == old_config.Cpu && - EmuConfig.Gamefixes == old_config.Gamefixes && - EmuConfig.Speedhacks == old_config.Speedhacks && - EmuConfig.Profiler == old_config.Profiler) + if (EmuConfig.Cpu == old_config.Cpu && EmuConfig.Gamefixes == old_config.Gamefixes && + EmuConfig.Speedhacks == old_config.Speedhacks && EmuConfig.Profiler == old_config.Profiler) { return; } @@ -1904,12 +1739,18 @@ void VMManager::CheckForPatchConfigChanges(const Pcsx2Config& old_config) { if (EmuConfig.EnableCheats == old_config.EnableCheats && EmuConfig.EnableWideScreenPatches == old_config.EnableWideScreenPatches && + EmuConfig.EnableNoInterlacingPatches == old_config.EnableNoInterlacingPatches && EmuConfig.EnablePatches == old_config.EnablePatches) { return; } - ReloadPatches(true, true); + Patch::UpdateActivePatches(true, false, true); + + // This is a bit messy, because the patch config update happens after the settings are loaded, + // if we disable widescreen patches, we have to reload the original settings again. + if (Patch::ReloadPatchAffectingOptions()) + GetMTGS().ApplySettings(); } void VMManager::CheckForDEV9ConfigChanges(const Pcsx2Config& old_config) @@ -1997,13 +1838,6 @@ void VMManager::CheckForConfigChanges(const Pcsx2Config& old_config) CheckForDEV9ConfigChanges(old_config); CheckForMemoryCardConfigChanges(old_config); USB::CheckForConfigChanges(old_config); - - if (EmuConfig.EnableCheats != old_config.EnableCheats || - EmuConfig.EnableWideScreenPatches != old_config.EnableWideScreenPatches || - EmuConfig.EnableNoInterlacingPatches != old_config.EnableNoInterlacingPatches) - { - VMManager::ReloadPatches(true, true); - } } // For the big picture UI, we still need to update GS settings, since it's running, @@ -2048,6 +1882,8 @@ bool VMManager::ReloadGameSettings() if (!UpdateGameSettingsLayer()) return false; + // Patches must come first, because they can affect aspect ratio/interlacing. + Patch::UpdateActivePatches(true, false, true); ApplySettings(); return true; } @@ -2070,7 +1906,8 @@ void VMManager::EnforceAchievementsChallengeModeSettings() // Can't use cheats. if (EmuConfig.EnableCheats) { - Host::AddKeyedOSDMessage("ChallengeDisableCheats", "Cheats have been disabled due to achievements hardcore mode.", Host::OSD_WARNING_DURATION); + Host::AddKeyedOSDMessage("ChallengeDisableCheats", + "Cheats have been disabled due to achievements hardcore mode.", Host::OSD_WARNING_DURATION); EmuConfig.EnableCheats = false; } @@ -2083,7 +1920,8 @@ void VMManager::EnforceAchievementsChallengeModeSettings() EmuConfig.GS.FrameratePAL = Pcsx2Config::GSOptions::DEFAULT_FRAME_RATE_PAL; // You can overclock, but not underclock (since that might slow down the game and make it easier). - EmuConfig.Speedhacks.EECycleRate = std::max(EmuConfig.Speedhacks.EECycleRate, 0); + EmuConfig.Speedhacks.EECycleRate = + std::max(EmuConfig.Speedhacks.EECycleRate, 0); EmuConfig.Speedhacks.EECycleSkip = 0; } @@ -2114,7 +1952,8 @@ void VMManager::WarnAboutUnsafeSettings() if (EmuConfig.Speedhacks.fastCDVD) messages += ICON_FA_COMPACT_DISC " Fast CDVD is enabled, this may break games.\n"; if (EmuConfig.Speedhacks.EECycleRate != 0 || EmuConfig.Speedhacks.EECycleSkip != 0) - messages += ICON_FA_TACHOMETER_ALT " Cycle rate/skip is not at default, this may crash or make games run too slow.\n"; + messages += + ICON_FA_TACHOMETER_ALT " Cycle rate/skip is not at default, this may crash or make games run too slow.\n"; if (EmuConfig.SPU2.SynchMode == Pcsx2Config::SPU2Options::SynchronizationMode::ASync) messages += ICON_FA_VOLUME_MUTE " Audio is using async mix, expect desynchronization in FMVs.\n"; if (EmuConfig.GS.UpscaleMultiplier < 1.0f) @@ -2122,28 +1961,35 @@ void VMManager::WarnAboutUnsafeSettings() if (EmuConfig.GS.HWMipmap != HWMipmapLevel::Automatic) messages += ICON_FA_IMAGES " Mipmapping is not set to automatic. This may break rendering in some games.\n"; if (EmuConfig.GS.TextureFiltering != BiFiltering::PS2) - messages += ICON_FA_FILTER " Texture filtering is not set to Bilinear (PS2). This will break rendering in some games.\n"; + messages += ICON_FA_FILTER + " Texture filtering is not set to Bilinear (PS2). This will break rendering in some games.\n"; if (EmuConfig.GS.TriFilter != TriFiltering::Automatic) - messages += ICON_FA_PAGER " Trilinear filtering is not set to automatic. This may break rendering in some games.\n"; + messages += + ICON_FA_PAGER " Trilinear filtering is not set to automatic. This may break rendering in some games.\n"; if (EmuConfig.GS.AccurateBlendingUnit <= AccBlendLevel::Minimum) messages += ICON_FA_BLENDER " Blending is below basic, this may break effects in some games.\n"; if (EmuConfig.GS.HWDownloadMode != GSHardwareDownloadMode::Enabled) - messages += ICON_FA_DOWNLOAD " Hardware Download Mode is not set to Accurate, this may break rendering in some games.\n"; + messages += ICON_FA_DOWNLOAD + " Hardware Download Mode is not set to Accurate, this may break rendering in some games.\n"; if (EmuConfig.Cpu.sseMXCSR.GetRoundMode() != SSEround_Chop) messages += ICON_FA_MICROCHIP " EE FPU Round Mode is not set to default, this may break some games.\n"; - if (!EmuConfig.Cpu.Recompiler.fpuOverflow || EmuConfig.Cpu.Recompiler.fpuExtraOverflow || EmuConfig.Cpu.Recompiler.fpuFullMode) + if (!EmuConfig.Cpu.Recompiler.fpuOverflow || EmuConfig.Cpu.Recompiler.fpuExtraOverflow || + EmuConfig.Cpu.Recompiler.fpuFullMode) messages += ICON_FA_MICROCHIP " EE FPU Clamp Mode is not set to default, this may break some games.\n"; - if (EmuConfig.Cpu.sseVU0MXCSR.GetRoundMode() != SSEround_Chop || EmuConfig.Cpu.sseVU1MXCSR.GetRoundMode() != SSEround_Chop) + if (EmuConfig.Cpu.sseVU0MXCSR.GetRoundMode() != SSEround_Chop || + EmuConfig.Cpu.sseVU1MXCSR.GetRoundMode() != SSEround_Chop) messages += ICON_FA_MICROCHIP " VU Round Mode is not set to default, this may break some games.\n"; - if (!EmuConfig.Cpu.Recompiler.vu0Overflow || EmuConfig.Cpu.Recompiler.vu0ExtraOverflow || EmuConfig.Cpu.Recompiler.vu0SignOverflow || - !EmuConfig.Cpu.Recompiler.vu1Overflow || EmuConfig.Cpu.Recompiler.vu1ExtraOverflow || EmuConfig.Cpu.Recompiler.vu1SignOverflow) + if (!EmuConfig.Cpu.Recompiler.vu0Overflow || EmuConfig.Cpu.Recompiler.vu0ExtraOverflow || + EmuConfig.Cpu.Recompiler.vu0SignOverflow || !EmuConfig.Cpu.Recompiler.vu1Overflow || + EmuConfig.Cpu.Recompiler.vu1ExtraOverflow || EmuConfig.Cpu.Recompiler.vu1SignOverflow) { messages += ICON_FA_MICROCHIP " VU Clamp Mode is not set to default, this may break some games.\n"; } if (!EmuConfig.EnableGameFixes) messages += ICON_FA_GAMEPAD " Game Fixes are not enabled. Compatibility with some games may be affected.\n"; if (!EmuConfig.EnablePatches) - messages += ICON_FA_GAMEPAD " Compatibility Patches are not enabled. Compatibility with some games may be affected.\n"; + messages += + ICON_FA_GAMEPAD " Compatibility Patches are not enabled. Compatibility with some games may be affected.\n"; if (EmuConfig.GS.FramerateNTSC != Pcsx2Config::GSOptions::DEFAULT_FRAME_RATE_NTSC) messages += ICON_FA_TV " Frame rate for NTSC is not default. This may break some games.\n"; if (EmuConfig.GS.FrameratePAL != Pcsx2Config::GSOptions::DEFAULT_FRAME_RATE_PAL) @@ -2164,13 +2010,17 @@ void VMManager::WarnAboutUnsafeSettings() messages.clear(); if (!EmuConfig.Cpu.Recompiler.EnableEE) - messages += ICON_FA_EXCLAMATION_CIRCLE " EE Recompiler is not enabled, this will significantly reduce performance.\n"; + messages += + ICON_FA_EXCLAMATION_CIRCLE " EE Recompiler is not enabled, this will significantly reduce performance.\n"; if (!EmuConfig.Cpu.Recompiler.EnableVU0) - messages += ICON_FA_EXCLAMATION_CIRCLE " VU0 Recompiler is not enabled, this will significantly reduce performance.\n"; + messages += + ICON_FA_EXCLAMATION_CIRCLE " VU0 Recompiler is not enabled, this will significantly reduce performance.\n"; if (!EmuConfig.Cpu.Recompiler.EnableVU1) - messages += ICON_FA_EXCLAMATION_CIRCLE " VU1 Recompiler is not enabled, this will significantly reduce performance.\n"; + messages += + ICON_FA_EXCLAMATION_CIRCLE " VU1 Recompiler is not enabled, this will significantly reduce performance.\n"; if (!EmuConfig.Cpu.Recompiler.EnableIOP) - messages += ICON_FA_EXCLAMATION_CIRCLE " IOP Recompiler is not enabled, this will significantly reduce performance.\n"; + messages += + ICON_FA_EXCLAMATION_CIRCLE " IOP Recompiler is not enabled, this will significantly reduce performance.\n"; if (EmuConfig.Cpu.Recompiler.EnableEECache) messages += ICON_FA_EXCLAMATION_CIRCLE " EE Cache is enabled, this will significantly reduce performance.\n"; if (!EmuConfig.Speedhacks.WaitLoop) @@ -2223,7 +2073,8 @@ void VMManager::SaveSessionTime() if (!s_game_serial.empty() && s_game_crc != 0) { // round up to seconds - const std::time_t etime = static_cast(std::round(Common::Timer::ConvertValueToSeconds(ctime - s_session_start_time))); + const std::time_t etime = + static_cast(std::round(Common::Timer::ConvertValueToSeconds(ctime - s_session_start_time))); const std::time_t wtime = std::time(nullptr); GameList::AddPlayedTimeForSerial(s_game_serial, wtime, etime); } @@ -2300,7 +2151,8 @@ static void InitializeCPUInfo() return; } - Console.WriteLn(Color_StrongYellow, "Processor count: %u cores, %u processors", cpuinfo_get_cores_count(), cpuinfo_get_processors_count()); + Console.WriteLn(Color_StrongYellow, "Processor count: %u cores, %u processors", cpuinfo_get_cores_count(), + cpuinfo_get_processors_count()); Console.WriteLn(Color_StrongYellow, "Cluster count: %u", cluster_count); static std::vector ordered_processors; @@ -2319,9 +2171,10 @@ static void InitializeCPUInfo() // find the large and small clusters based on frequency // this is assuming the large cluster is always clocked higher // sort based on core, so that hyperthreads get pushed down - std::sort(ordered_processors.begin(), ordered_processors.end(), [](const cpuinfo_processor* lhs, const cpuinfo_processor* rhs) { - return (lhs->core->frequency > rhs->core->frequency || lhs->smt_id < rhs->smt_id); - }); + std::sort(ordered_processors.begin(), ordered_processors.end(), + [](const cpuinfo_processor* lhs, const cpuinfo_processor* rhs) { + return (lhs->core->frequency > rhs->core->frequency || lhs->smt_id < rhs->smt_id); + }); s_processor_list.reserve(ordered_processors.size()); std::stringstream ss; @@ -2357,14 +2210,15 @@ static void SetMTVUAndAffinityControlDefault(SettingsInterface& si) for (u32 i = 0; i < cluster_count; i++) { const cpuinfo_cluster* cluster = cpuinfo_get_cluster(i); - Console.WriteLn(" Cluster %u: %u cores and %u processors at %u MHz", - i, cluster->core_count, cluster->processor_count, static_cast(cluster->frequency /* / 1000000u*/)); + Console.WriteLn(" Cluster %u: %u cores and %u processors at %u MHz", i, cluster->core_count, + cluster->processor_count, static_cast(cluster->frequency /* / 1000000u*/)); } const bool has_big_little = cluster_count > 1; Console.WriteLn("Big-Little: %s", has_big_little ? "yes" : "no"); - const u32 big_cores = cpuinfo_get_cluster(0)->core_count + ((cluster_count > 2) ? cpuinfo_get_cluster(1)->core_count : 0u); + const u32 big_cores = + cpuinfo_get_cluster(0)->core_count + ((cluster_count > 2) ? cpuinfo_get_cluster(1)->core_count : 0u); Console.WriteLn("Guessing we have %u big/medium cores...", big_cores); if (big_cores >= 3) @@ -2448,8 +2302,7 @@ void VMManager::SetEmuThreadAffinities() return; } - if (EmuConfig.Cpu.AffinityControlMode == 0 || - s_processor_list.size() < (EmuConfig.Speedhacks.vuThread ? 3 : 2)) + if (EmuConfig.Cpu.AffinityControlMode == 0 || s_processor_list.size() < (EmuConfig.Speedhacks.vuThread ? 3 : 2)) { if (EmuConfig.Cpu.AffinityControlMode != 0) Console.Error("Insufficient processors for affinity control."); @@ -2472,12 +2325,13 @@ void VMManager::SetEmuThreadAffinities() }; // steal vu's thread if mtvu is off - const u8* this_proc_assigment = processor_assignment[EmuConfig.Cpu.AffinityControlMode][EmuConfig.Speedhacks.vuThread]; + const u8* this_proc_assigment = + processor_assignment[EmuConfig.Cpu.AffinityControlMode][EmuConfig.Speedhacks.vuThread]; const u32 ee_index = s_processor_list[this_proc_assigment[0]]; const u32 vu_index = s_processor_list[this_proc_assigment[1]]; const u32 gs_index = s_processor_list[this_proc_assigment[2]]; - Console.WriteLn("Processor order assignment: EE=%u, VU=%u, GS=%u", - this_proc_assigment[0], this_proc_assigment[1], this_proc_assigment[2]); + Console.WriteLn("Processor order assignment: EE=%u, VU=%u, GS=%u", this_proc_assigment[0], this_proc_assigment[1], + this_proc_assigment[2]); const u64 ee_affinity = static_cast(1) << ee_index; Console.WriteLn(Color_StrongGreen, "EE thread is on processor %u (0x%llx)", ee_index, ee_affinity); diff --git a/pcsx2/VMManager.h b/pcsx2/VMManager.h index dd54b4a017..a291d8c6ad 100644 --- a/pcsx2/VMManager.h +++ b/pcsx2/VMManager.h @@ -107,9 +107,6 @@ namespace VMManager /// Reloads game specific settings, and applys any changes present. bool ReloadGameSettings(); - /// Reloads cheats/patches. If verbose is set, the number of patches loaded will be shown in the OSD. - void ReloadPatches(bool verbose, bool show_messages_when_disabled); - /// Returns the save state filename for the given game serial/crc. std::string GetSaveStateFileName(const char* game_serial, u32 game_crc, s32 slot); diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index de0a8cf0dc..3e167a89ef 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -259,7 +259,6 @@ - Create diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters index 6a5ffb4410..06e5aacba5 100644 --- a/pcsx2/pcsx2.vcxproj.filters +++ b/pcsx2/pcsx2.vcxproj.filters @@ -692,9 +692,6 @@ Misc - - Misc - System\Ps2\IPU diff --git a/pcsx2/x86/ix86-32/iR5900-32.cpp b/pcsx2/x86/ix86-32/iR5900-32.cpp index 24889dd75a..ffe71fa2ef 100644 --- a/pcsx2/x86/ix86-32/iR5900-32.cpp +++ b/pcsx2/x86/ix86-32/iR5900-32.cpp @@ -1694,7 +1694,7 @@ void recompileNextInstruction(bool delayslot, bool swapped_delay_slot) int count; if (EmuConfig.EnablePatches) - ApplyDynamicPatches(pc); + Patch::ApplyDynamicPatches(pc); // add breakpoint if (!delayslot) diff --git a/tools/merge_ws_ni_patches.py b/tools/merge_ws_ni_patches.py new file mode 100644 index 0000000000..050728ccbf --- /dev/null +++ b/tools/merge_ws_ni_patches.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +# PCSX2 - PS2 Emulator for PCs +# Copyright (C) 2023 PCSX2 Dev Team +# +# PCSX2 is free software: you can redistribute it and/or modify it under the terms +# of the GNU Lesser General Public License as published by the Free Software Found- +# ation, either version 3 of the License, or (at your option) any later version. +# +# PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with PCSX2. +# If not, see . + +# pylint: disable=bare-except, disable=missing-function-docstring + +import glob +import os +import sys + +def merge_patches(srcdir, dstdir, label, desc, extralines=None): + for file in glob.glob(os.path.join(srcdir, "*.pnach")): + print(f"Reading {file}...") + + name = os.path.basename(file) + with open(file, "rb") as f: + lines = f.read().decode().strip().split("\n") + + gametitle_line = None + comment_line = None + for line in lines: + line = line.strip() + if line.startswith("gametitle=") and gametitle_line is None: + gametitle_line = line + elif line.startswith("comment=") and comment_line is None: + comment_line = line[8:] + + # ignore gametitle if file already exists + outname = os.path.join(dstdir, name) + if os.path.exists(outname): + gametitle_line = None + + with open(outname, "ab") as f: + if gametitle_line is not None: + f.write((gametitle_line + "\n\n").encode()) + + f.write(f"[{label}]\n".encode()) + if desc is not None and comment_line is None: + f.write(f"description={desc}\n".encode()) + if extralines is not None: + f.write(f"{extralines}\n".encode()) + for line in lines: + line = line.strip() + if not line.startswith("gametitle="): + f.write((line + "\n").encode()) + f.write("\n\n".encode()) + + print(f"Wrote/updated {outname}") + + +if __name__ == "__main__": + if len(sys.argv) < 4: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + + outdir = sys.argv[3] + if not os.path.isdir(outdir): + os.mkdir(outdir) + + merge_patches(sys.argv[1], outdir, "Widescreen 16:9", "Renders the game in 16:9 aspect ratio, instead of 4:3.", "gsaspectratio=16:9") + merge_patches(sys.argv[2], outdir, "No-Interlacing", "Attempts to disable interlaced offset rendering.", "gsinterlacemode=1")