Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qt: Allow loading custom pnach patches and bundled patches simultaneously #10322

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion pcsx2-qt/Settings/GamePatchSettingsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ GamePatchSettingsWidget::GamePatchSettingsWidget(SettingsWindow* dialog, QWidget
m_ui.scrollArea->setFrameShape(QFrame::WinPanel);
m_ui.scrollArea->setFrameShadow(QFrame::Sunken);

setUnlabeledPatchesWarningVisibility(false);

connect(m_ui.reload, &QPushButton::clicked, this, &GamePatchSettingsWidget::onReloadClicked);

reloadList();
Expand All @@ -86,10 +88,12 @@ void GamePatchSettingsWidget::onReloadClicked()
void GamePatchSettingsWidget::reloadList()
{
// Patches shouldn't have any unlabelled patch groups, because they're new.
std::vector<Patch::PatchInfo> patches = Patch::GetPatchInfo(m_dialog->getSerial(), m_dialog->getDiscCRC(), false, nullptr);
u32 numberOfUnlabeledPatches = 0;
Daniel-McCarthy marked this conversation as resolved.
Show resolved Hide resolved
std::vector<Patch::PatchInfo> patches = Patch::GetPatchInfo(m_dialog->getSerial(), m_dialog->getDiscCRC(), false, &numberOfUnlabeledPatches);
std::vector<std::string> enabled_list =
m_dialog->getSettingsInterface()->GetStringList(Patch::PATCHES_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY);

setUnlabeledPatchesWarningVisibility(numberOfUnlabeledPatches > 0);
delete m_ui.scrollArea->takeWidget();

QWidget* container = new QWidget(m_ui.scrollArea);
Expand Down Expand Up @@ -130,3 +134,7 @@ void GamePatchSettingsWidget::reloadList()

m_ui.scrollArea->setWidget(container);
}

void GamePatchSettingsWidget::setUnlabeledPatchesWarningVisibility(bool visible) {
m_ui.unlabeledPatchWarning->setVisible(visible);
}
1 change: 1 addition & 0 deletions pcsx2-qt/Settings/GamePatchSettingsWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ private Q_SLOTS:

private:
void reloadList();
void setUnlabeledPatchesWarningVisibility(bool visible);

Ui::GamePatchSettingsWidget m_ui;
SettingsWindow* m_dialog;
Expand Down
7 changes: 7 additions & 0 deletions pcsx2-qt/Settings/GamePatchSettingsWidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="unlabeledPatchWarning">
<property name="text">
<string>Any patches bundled with PCSX2 for this game will be disabled since you have unlabeled patches loaded.</string>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
Expand Down
85 changes: 80 additions & 5 deletions pcsx2/Patch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,13 @@ namespace Patch
static std::vector<std::string> FindPatchFilesOnDisk(
const std::string_view& serial, u32 crc, bool cheats, bool all_crcs);

static bool ContainsPatchName(const PatchInfoList& patches, const std::string_view& patchName);
Daniel-McCarthy marked this conversation as resolved.
Show resolved Hide resolved
static bool ContainsPatchName(const PatchList& patches, const std::string_view& patchName);

template <typename F>
static void EnumeratePnachFiles(const std::string_view& serial, u32 crc, bool cheats, bool for_ui, const F& f);

static bool PatchStringHasUnlabelledPatch(const std::string& pnach_data);
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);
Expand Down Expand Up @@ -215,6 +219,13 @@ void Patch::TrimPatchLine(std::string& buffer)
buffer.erase(pos);
}

bool Patch::ContainsPatchName(const PatchList& patchList, const std::string_view& patchName)
{
return std::find_if(patchList.begin(), patchList.end(), [&patchName](const PatchGroup& patch) {
return patch.name == patchName;
}) != patchList.end();
}

int Patch::PatchTableExecute(PatchGroup* group, const std::string_view& lhs, const std::string_view& rhs,
const std::span<const PatchTextTable>& Table)
{
Expand Down Expand Up @@ -267,7 +278,15 @@ u32 Patch::LoadPatchesFromString(PatchList* patch_list, const std::string& patch

if (!current_patch_group.name.empty() || !current_patch_group.patches.empty())
{
patch_list->push_back(std::move(current_patch_group));
// Don't show patches with duplicate names, prefer the first loaded.
if (!ContainsPatchName(*patch_list, current_patch_group.name))
{
patch_list->push_back(std::move(current_patch_group));
}
else
{
Console.WriteLn(Color_Gray, fmt::format("Patch: Skipped loading patch '{}' since with duplicate name was already loaded.", current_patch_group.name));
Daniel-McCarthy marked this conversation as resolved.
Show resolved Hide resolved
}
current_patch_group = {};
}

Expand Down Expand Up @@ -350,6 +369,13 @@ std::vector<std::string> Patch::FindPatchFilesOnDisk(const std::string_view& ser
return ret;
}

bool Patch::ContainsPatchName(const PatchInfoList& patches, const std::string_view& patchName)
Daniel-McCarthy marked this conversation as resolved.
Show resolved Hide resolved
{
return std::find_if(patches.begin(), patches.end(), [&patchName](const PatchInfo& patch) {
return patch.name == patchName;
}) != patches.end();
}

template <typename F>
void Patch::EnumeratePnachFiles(const std::string_view& serial, u32 crc, bool cheats, bool for_ui, const F& f)
{
Expand All @@ -358,20 +384,28 @@ void Patch::EnumeratePnachFiles(const std::string_view& serial, u32 crc, bool ch
if (for_ui || !Achievements::IsHardcoreModeActive())
disk_patch_files = FindPatchFilesOnDisk(serial, crc, cheats, for_ui);

bool unlabeled_patch_found = false;
if (!disk_patch_files.empty())
{
for (const std::string& file : disk_patch_files)
{
std::optional<std::string> contents = FileSystem::ReadFileToString(file.c_str());
if (contents.has_value())
{
// Catch if unlabeled patches are being loaded so we can disable ZIP patches to prevent conflicts.
if (PatchStringHasUnlabelledPatch(contents.value()))
{
unlabeled_patch_found = true;
Console.WriteLn(fmt::format("Patch: Disabling any bundled '{}' patches due to unlabeled patch being loaded. (To avoid conflicts)", PATCHES_ZIP_NAME));
}

f(std::move(file), std::move(contents.value()));
}
}

return;
}

// Otherwise fall back to the zip.
if (cheats || !OpenPatchesZip())
if (cheats || unlabeled_patch_found || !OpenPatchesZip())
return;

// Prefer filename with serial.
Expand All @@ -386,6 +420,39 @@ void Patch::EnumeratePnachFiles(const std::string_view& serial, u32 crc, bool ch
f(std::move(zip_filename), std::move(pnach_data.value()));
}

bool Patch::PatchStringHasUnlabelledPatch(const std::string& pnach_data)
{
std::istringstream ss(pnach_data);
Daniel-McCarthy marked this conversation as resolved.
Show resolved Hide resolved
std::string line;
bool foundPatch = false, foundLabel = false;

while (std::getline(ss, line))
{
TrimPatchLine(line);
if (line.empty())
continue;

if (line.length() > 2 && line.front() == '[' && line.back() == ']')
{
if (!foundPatch)
return false;
foundLabel = true;
continue;
}

std::string_view key, value;
StringUtil::ParseAssignmentString(line, &key, &value);
if (key == "patch")
{
if (!foundLabel)
return true;

foundPatch = true;
}
}
return false;
}

void Patch::ExtractPatchInfo(PatchInfoList* dst, const std::string& pnach_data, u32* num_unlabelled_patches)
{
std::istringstream ss(pnach_data);
Expand All @@ -406,7 +473,15 @@ void Patch::ExtractPatchInfo(PatchInfoList* dst, const std::string& pnach_data,
if (std::none_of(dst->begin(), dst->end(),
[&current_patch](const PatchInfo& pi) { return (pi.name == current_patch.name); }))
{
dst->push_back(std::move(current_patch));
// Don't show patches with duplicate names, prefer the first loaded.
if (!ContainsPatchName(*dst, current_patch.name))
{
dst->push_back(std::move(current_patch));
}
else
{
Console.WriteLn(Color_Gray, fmt::format("Patch: Skipped reading patch '{}' due patch with duplicate name being loaded.", current_patch.name));
Daniel-McCarthy marked this conversation as resolved.
Show resolved Hide resolved
}
}
current_patch = {};
}
Expand Down