diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt
index 619cc3932f..c76fbfeca1 100644
--- a/pcsx2-qt/CMakeLists.txt
+++ b/pcsx2-qt/CMakeLists.txt
@@ -96,6 +96,11 @@ target_sources(pcsx2-qt PRIVATE
Settings/InterfaceSettingsWidget.cpp
Settings/InterfaceSettingsWidget.h
Settings/InterfaceSettingsWidget.ui
+ Settings/MemoryCardConvertDialog.cpp
+ Settings/MemoryCardConvertDialog.h
+ Settings/MemoryCardConvertDialog.ui
+ Settings/MemoryCardConvertWorker.cpp
+ Settings/MemoryCardConvertWorker.h
Settings/MemoryCardSettingsWidget.cpp
Settings/MemoryCardSettingsWidget.h
Settings/MemoryCardSettingsWidget.ui
diff --git a/pcsx2-qt/Settings/MemoryCardConvertDialog.cpp b/pcsx2-qt/Settings/MemoryCardConvertDialog.cpp
new file mode 100644
index 0000000000..67abbf4f85
--- /dev/null
+++ b/pcsx2-qt/Settings/MemoryCardConvertDialog.cpp
@@ -0,0 +1,310 @@
+/* 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 "MemoryCardConvertDialog.h"
+
+#include
+#include
+#include
+
+#include "common/Path.h"
+#include "common/StringUtil.h"
+
+#include "pcsx2/System.h"
+
+MemoryCardConvertDialog::MemoryCardConvertDialog(QWidget* parent, QString selectedCard)
+ : QDialog(parent)
+{
+ m_ui.setupUi(this);
+ m_selectedCard = selectedCard;
+ std::optional srcCardInfo = FileMcd_GetCardInfo(m_selectedCard.toStdString());
+
+ if (srcCardInfo.has_value())
+ {
+ m_srcCardInfo = srcCardInfo.value();
+ }
+
+ isSetup = SetupPicklist();
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ m_ui.progressBar->setRange(0, 100);
+ m_ui.progressBar->setValue(0);
+
+ connect(m_ui.conversionTypeSelect, &QComboBox::currentIndexChanged, this, [this]()
+ {
+ switch (m_srcCardInfo.type)
+ {
+ case MemoryCardType::File:
+ SetType(MemoryCardType::Folder, MemoryCardFileType::Unknown, "Uses a folder on your PC filesystem, instead of a file. Infinite capacity, while keeping the same compatibility as an 8 MB memory card.");
+ break;
+ case MemoryCardType::Folder:
+ switch (m_ui.conversionTypeSelect->currentData().toInt())
+ {
+ case 8:
+ SetType(MemoryCardType::File, MemoryCardFileType::PS2_8MB, "A standard, 8 MB memory card. Most compatible, but smallest capacity.");
+ break;
+ case 16:
+ SetType(MemoryCardType::File, MemoryCardFileType::PS2_16MB, "2x larger as a standard memory card. May have some compatibility issues.");
+ break;
+ case 32:
+ SetType(MemoryCardType::File, MemoryCardFileType::PS2_32MB, "4x larger than a standard memory card. Likely to have compatibility issues.");
+ break;
+ case 64:
+ SetType(MemoryCardType::File, MemoryCardFileType::PS2_64MB, "8x larger than a standard memory card. Likely to have compatibility issues.");
+ break;
+ default:
+ QMessageBox::critical(this, tr("Convert Memory Card Failed"), tr("Invalid MemoryCardType"));
+ return;
+ }
+ break;
+ default:
+ QMessageBox::critical(this, tr("Convert Memory Card Failed"), tr("Invalid MemoryCardType"));
+ return;
+ }
+ }
+ );
+
+ disconnect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, nullptr);
+
+ connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &MemoryCardConvertDialog::ConvertCard);
+ connect(m_ui.buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &MemoryCardConvertDialog::close);
+}
+
+MemoryCardConvertDialog::~MemoryCardConvertDialog() = default;
+
+bool MemoryCardConvertDialog::IsSetup()
+{
+ return isSetup;
+}
+
+void MemoryCardConvertDialog::onStatusUpdated()
+{
+
+}
+
+void MemoryCardConvertDialog::onProgressUpdated(int value, int range)
+{
+ m_ui.progressBar->setRange(0, range);
+ m_ui.progressBar->setValue(value);
+}
+
+void MemoryCardConvertDialog::onThreadFinished()
+{
+ QMessageBox::information(this, tr("Conversion Complete"), tr("Memory card \"%1\" converted to \"%2\"").arg(m_selectedCard).arg(m_destCardName));
+ accept();
+}
+
+void MemoryCardConvertDialog::StartThread()
+{
+ m_thread = std::make_unique(this, m_srcCardInfo.type, m_fileType, m_selectedCard.toStdString(), m_destCardName.toStdString());
+ connect(m_thread.get(), &MemoryCardConvertWorker::statusUpdated, this, &MemoryCardConvertDialog::onStatusUpdated);
+ connect(m_thread.get(), &MemoryCardConvertWorker::progressUpdated, this, &MemoryCardConvertDialog::onProgressUpdated);
+ connect(m_thread.get(), &MemoryCardConvertWorker::threadFinished, this, &MemoryCardConvertDialog::onThreadFinished);
+ m_thread->start();
+ UpdateEnabled();
+}
+
+void MemoryCardConvertDialog::CancelThread()
+{
+ if (!m_thread)
+ {
+ return;
+ }
+
+ m_thread->requestInterruption();
+ m_thread->join();
+ m_thread.reset();
+}
+
+void MemoryCardConvertDialog::UpdateEnabled()
+{
+ if (m_thread)
+ {
+ m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
+ m_ui.buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
+ }
+}
+
+bool MemoryCardConvertDialog::SetupPicklist()
+{
+ FileSystem::FindResultsArray rootDir;
+ size_t sizeBytes = 0;
+ bool typeSet = false;
+
+ m_ui.conversionTypeSelect->clear();
+
+ switch (m_srcCardInfo.type)
+ {
+ case MemoryCardType::File:
+ m_ui.conversionTypeSelect->addItems({"Folder"});
+ SetType(MemoryCardType::Folder, MemoryCardFileType::Unknown, "Uses a folder on your PC filesystem, instead of a file. Infinite capacity, while keeping the same compatibility as an 8 MB memory card.");
+ break;
+ case MemoryCardType::Folder:
+ // Compute which file types should be allowed.
+ FileSystem::FindFiles(m_srcCardInfo.path.c_str(), "*", FLAGS, &rootDir);
+
+ for (auto dirEntry : rootDir)
+ {
+ const std::string_view fileName = Path::GetFileName(dirEntry.FileName);
+
+ if (fileName.size() >= 7 && fileName.substr(0, 7).compare("_pcsx2_") == 0)
+ {
+ continue;
+ }
+ else if (dirEntry.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY)
+ {
+ sizeBytes += 512;
+ }
+ else
+ {
+ size_t toAdd = static_cast(dirEntry.Size + (1024 - (dirEntry.Size % 1024)));
+ sizeBytes += toAdd + 512; // The file content needs to be added, PLUS a directory entry
+ }
+ }
+
+ // Finally, round up to the nearest erase block.
+ sizeBytes += (512 * 16) - (sizeBytes % (512 * 16));
+
+ if (sizeBytes < CardCapacity::_8_MB)
+ {
+ m_ui.conversionTypeSelect->addItem("8 MB File", 8);
+
+ if (!typeSet)
+ {
+ SetType_8();
+ typeSet = true;
+ }
+ }
+
+ if (sizeBytes < CardCapacity::_16_MB)
+ {
+ m_ui.conversionTypeSelect->addItem("16 MB File", 16);
+
+ if (!typeSet)
+ {
+ SetType_16();
+ typeSet = true;
+ }
+ }
+
+ if (sizeBytes < CardCapacity::_32_MB)
+ {
+ m_ui.conversionTypeSelect->addItem("32 MB File", 32);
+
+ if (!typeSet)
+ {
+ SetType_32();
+ typeSet = true;
+ }
+ }
+
+ if (sizeBytes < CardCapacity::_64_MB)
+ {
+ m_ui.conversionTypeSelect->addItem("64 MB File", 64);
+
+ if (!typeSet)
+ {
+ SetType_64();
+ typeSet = true;
+ }
+ }
+
+ if (!typeSet)
+ {
+ QMessageBox::critical(this, tr("Cannot Convert Memory Card"), tr("Your folder memory card has too much data inside it to be converted to a file memory card. The largest supported file memory card has a capacity of 64 MB. To convert your folder memory card, you must remove game folders until its size is 64 MB or less."));
+ return false;
+ }
+
+ break;
+ default:
+ QMessageBox::critical(this, tr("Convert Memory Card Failed"), tr("Invalid MemoryCardType"));
+ return false;
+ }
+
+ return true;
+}
+
+void MemoryCardConvertDialog::ConvertCard()
+{
+ if (m_thread)
+ {
+ CancelThread();
+ }
+ else
+ {
+ QString baseName = m_selectedCard;
+
+ // Get our destination file name
+ size_t extensionPos = baseName.lastIndexOf(".ps2", -1);
+ // Strip the extension off of it
+ baseName.replace(extensionPos, 4, "");
+ // Add _converted to the end of it
+ baseName.append("_converted");
+
+ size_t num = 0;
+ QString destName = baseName;
+ destName.append(".ps2");
+
+ // If a match is found, revert back to the base name, add a number and the extension, and try again.
+ // Keep incrementing the number until we get a unique result.
+ while (m_srcCardInfo.type == MemoryCardType::File ? FileSystem::DirectoryExists(Path::Combine(EmuFolders::MemoryCards, destName.toStdString()).c_str()) : FileSystem::FileExists(Path::Combine(EmuFolders::MemoryCards, destName.toStdString()).c_str()))
+ {
+ destName = baseName;
+ destName.append(StringUtil::StdStringFromFormat("_%02d.ps2", ++num).c_str());
+ }
+
+ m_destCardName = destName;
+ StartThread();
+ }
+}
+
+void MemoryCardConvertDialog::ConvertCallback()
+{
+ Console.WriteLn("%s() Finished", __FUNCTION__);
+}
+
+void MemoryCardConvertDialog::SetType(MemoryCardType type, MemoryCardFileType fileType, const QString& description)
+{
+ m_type = type;
+ m_fileType = fileType;
+ m_ui.conversionTypeDescription->setPlainText(description);
+}
+
+void MemoryCardConvertDialog::SetType_8()
+{
+ SetType(MemoryCardType::File, MemoryCardFileType::PS2_8MB, "A standard, 8 MB memory card. Most compatible, but smallest capacity.");
+}
+
+void MemoryCardConvertDialog::SetType_16()
+{
+ SetType(MemoryCardType::File, MemoryCardFileType::PS2_16MB, "2x larger as a standard memory card. May have some compatibility issues.");
+}
+
+void MemoryCardConvertDialog::SetType_32()
+{
+ SetType(MemoryCardType::File, MemoryCardFileType::PS2_32MB, "4x larger than a standard memory card. Likely to have compatibility issues.");
+}
+
+void MemoryCardConvertDialog::SetType_64()
+{
+ SetType(MemoryCardType::File, MemoryCardFileType::PS2_64MB, "8x larger than a standard memory card. Likely to have compatibility issues.");
+}
+
+void MemoryCardConvertDialog::SetType_Folder()
+{
+ SetType(MemoryCardType::Folder, MemoryCardFileType::Unknown, "Uses a folder on your PC filesystem, instead of a file. Infinite capacity, while keeping the same compatibility as an 8 MB memory card.");
+}
diff --git a/pcsx2-qt/Settings/MemoryCardConvertDialog.h b/pcsx2-qt/Settings/MemoryCardConvertDialog.h
new file mode 100644
index 0000000000..fdbe0d3490
--- /dev/null
+++ b/pcsx2-qt/Settings/MemoryCardConvertDialog.h
@@ -0,0 +1,78 @@
+/* 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 .
+ */
+
+#pragma once
+
+#include
+
+#include "common/FileSystem.h"
+
+#include "ui_MemoryCardConvertDialog.h"
+
+#include "MemoryCardConvertWorker.h"
+
+#include "pcsx2/MemoryCardFile.h"
+
+class MemoryCardConvertDialog final : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit MemoryCardConvertDialog(QWidget* parent, QString selectedCard);
+ ~MemoryCardConvertDialog();
+
+ bool IsSetup();
+ void onStatusUpdated();
+ void onProgressUpdated(int value, int range);
+ void onThreadFinished();
+
+private Q_SLOTS:
+ void ConvertCard();
+ void ConvertCallback();
+
+private:
+ void StartThread();
+ void CancelThread();
+ void UpdateEnabled();
+ bool SetupPicklist();
+ void SetType(MemoryCardType type, MemoryCardFileType fileType, const QString& description);
+ void SetType_8();
+ void SetType_16();
+ void SetType_32();
+ void SetType_64();
+ void SetType_Folder();
+
+ Ui::MemoryCardConvertDialog m_ui;
+
+ bool isSetup = false;
+ AvailableMcdInfo m_srcCardInfo;
+ QString m_selectedCard;
+ QString m_destCardName;
+
+ MemoryCardType m_type = MemoryCardType::File;
+ MemoryCardFileType m_fileType = MemoryCardFileType::PS2_8MB;
+ std::unique_ptr m_thread;
+
+ static constexpr u32 FLAGS = FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_FILES;
+};
+
+// Card capacities computed from freshly formatted superblocks.
+namespace CardCapacity
+{
+ static constexpr size_t _8_MB = 0x1f40 * 512 * 2; //(0x1fc7 - 0x29) * 2 * 512;
+ static constexpr size_t _16_MB = 0x3e80 * 512 * 2; //(0x3fa7 - 0x49) * 2 * 512;
+ static constexpr size_t _32_MB = 0x7d00 * 512 * 2; //(0x7f67 - 0x89) * 2 * 512;
+ static constexpr size_t _64_MB = 0xfde8 * 512 * 2; //(0xfee7 - 0x0109) * 2 * 512;
+}
diff --git a/pcsx2-qt/Settings/MemoryCardConvertDialog.ui b/pcsx2-qt/Settings/MemoryCardConvertDialog.ui
new file mode 100644
index 0000000000..0b94100260
--- /dev/null
+++ b/pcsx2-qt/Settings/MemoryCardConvertDialog.ui
@@ -0,0 +1,181 @@
+
+
+ MemoryCardConvertDialog
+
+
+ true
+
+
+
+ 0
+ 0
+ 440
+ 320
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 440
+ 320
+
+
+
+
+ 440
+ 320
+
+
+
+ Convert Memory Card
+
+
+ false
+
+
+ false
+
+
+
+
+ 10
+ 10
+ 421
+ 61
+
+
+
+ Conversion Type
+
+
+
+
+ 10
+ 30
+ 401
+ 22
+
+
+ -
+
+ 8 MB File
+
+
+ -
+
+ 16 MB File
+
+
+ -
+
+ 32 MB File
+
+
+ -
+
+ 64 MB File
+
+
+ -
+
+ Folder
+
+
+
+
+
+
+
+ 10
+ 150
+ 421
+ 61
+
+
+
+
+ 0
+ 0
+
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+hr { height: 1px; border-width: 0; }
+</style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;">
+<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Note: Converting a memory card creates a COPY of your existing memory card. It does NOT delete, modify, or replace your existing memory card.</p></body></html>
+
+
+
+
+
+ 10
+ 80
+ 421
+ 61
+
+
+
+
+ 0
+ 0
+
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+hr { height: 1px; border-width: 0; }
+</style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;">
+<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html>
+
+
+
+
+
+ 270
+ 290
+ 156
+ 24
+
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+ 10
+ 220
+ 421
+ 61
+
+
+
+ Progress
+
+
+
+
+ 10
+ 30
+ 401
+ 23
+
+
+
+ 24
+
+
+
+
+
+
+
diff --git a/pcsx2-qt/Settings/MemoryCardConvertWorker.cpp b/pcsx2-qt/Settings/MemoryCardConvertWorker.cpp
new file mode 100644
index 0000000000..fa397468e4
--- /dev/null
+++ b/pcsx2-qt/Settings/MemoryCardConvertWorker.cpp
@@ -0,0 +1,165 @@
+
+#include "PrecompiledHeader.h"
+
+#include "MemoryCardConvertWorker.h"
+
+#include "common/Path.h"
+#include "common/FileSystem.h"
+
+MemoryCardConvertWorker::MemoryCardConvertWorker(QWidget* parent, MemoryCardType type, MemoryCardFileType fileType, const std::string& srcFileName, const std::string& destFileName)
+ : QtAsyncProgressThread(parent)
+{
+ this->type = type;
+ this->fileType = fileType;
+ this->srcFileName = srcFileName;
+ this->destFileName = destFileName;
+}
+
+MemoryCardConvertWorker::~MemoryCardConvertWorker() = default;
+
+void MemoryCardConvertWorker::runAsync()
+{
+ switch (type)
+ {
+ case MemoryCardType::File:
+ ConvertToFolder(srcFileName, destFileName, fileType);
+ break;
+ case MemoryCardType::Folder:
+ ConvertToFile(srcFileName, destFileName, fileType);
+ break;
+ default:
+ break;
+ }
+}
+
+bool MemoryCardConvertWorker::ConvertToFile(const std::string& srcFolderName, const std::string& destFileName, const MemoryCardFileType type)
+{
+ const std::string srcPath(Path::Combine(EmuFolders::MemoryCards, srcFolderName));
+ const std::string destPath(Path::Combine(EmuFolders::MemoryCards, destFileName));
+ size_t sizeInMB = 0;
+
+ switch (type)
+ {
+ case MemoryCardFileType::PS2_8MB:
+ sizeInMB = 8;
+ break;
+ case MemoryCardFileType::PS2_16MB:
+ sizeInMB = 16;
+ break;
+ case MemoryCardFileType::PS2_32MB:
+ sizeInMB = 32;
+ break;
+ case MemoryCardFileType::PS2_64MB:
+ sizeInMB = 64;
+ break;
+ default:
+ Console.Error("%s(%s, %s, %d) Received invalid MemoryCardFileType, aborting", __FUNCTION__, srcPath.c_str(), destPath.c_str(), type);
+ return false;
+ }
+
+ FolderMemoryCard sourceFolderMemoryCard;
+ Pcsx2Config::McdOptions config;
+ config.Enabled = true;
+ config.Type = MemoryCardType::Folder;
+ sourceFolderMemoryCard.Open(srcPath, config, (sizeInMB * 1024 * 1024) / FolderMemoryCard::ClusterSize, false, "");
+ const size_t capacity = sourceFolderMemoryCard.GetSizeInClusters() * FolderMemoryCard::ClusterSizeRaw;
+
+ std::vector sourceBuffer;
+ sourceBuffer.resize(capacity);
+ size_t address = 0;
+ this->SetProgressRange(capacity);
+ this->SetProgressValue(0);
+
+ while (address < capacity)
+ {
+ sourceFolderMemoryCard.Read(sourceBuffer.data() + address, address, FolderMemoryCard::PageSizeRaw);
+ address += FolderMemoryCard::PageSizeRaw;
+
+ // Only report progress every 16 pages. Substantially speeds up the conversion.
+ if (address % (FolderMemoryCard::PageSizeRaw * 16) == 0)
+ this->SetProgressValue(address);
+ }
+
+ bool writeResult = FileSystem::WriteBinaryFile(destPath.c_str(), sourceBuffer.data(), sourceBuffer.size());
+
+ if (!writeResult)
+ {
+ Console.Error("%s(%s, %s, %d) Failed to write memory card contents to file", __FUNCTION__, srcPath.c_str(), destPath.c_str(), type);
+ return false;
+ }
+#ifdef _WIN32
+ else
+ {
+ FileSystem::SetPathCompression(destPath.c_str(), true);
+ }
+#endif
+
+ sourceFolderMemoryCard.Close(false);
+ return true;
+}
+
+bool MemoryCardConvertWorker::ConvertToFolder(const std::string& srcFileName, const std::string& destFolderName, const MemoryCardFileType type)
+{
+ const std::string srcPath(Path::Combine(EmuFolders::MemoryCards, srcFileName));
+ const std::string destPath(Path::Combine(EmuFolders::MemoryCards, destFolderName));
+
+ u8 buffer[FolderMemoryCard::PageSizeRaw];
+ FolderMemoryCard targetFolderMemoryCard;
+ Pcsx2Config::McdOptions config;
+ config.Enabled = true;
+ config.Type = MemoryCardType::Folder;
+
+ std::optional> sourceBufferOpt = FileSystem::ReadBinaryFile(srcPath.c_str());
+
+ if (!sourceBufferOpt.has_value())
+ {
+ Console.Error("%s(%s, %s, %d) Failed to open file memory card!", __FUNCTION__, srcFileName.c_str(), destFolderName.c_str(), type);
+ return false;
+ }
+
+ std::vector sourceBuffer = sourceBufferOpt.value();
+ // Set progress bar to the literal number of bytes in the memcard.
+ // Plus two because there is a lag period after the Save calls complete
+ // where the progress bar stalls out; this lets us stop the progress bar
+ // just shy of 50 and 100% so it seems like its still doing some work.
+ this->SetProgressRange((sourceBuffer.size() * 2) + 2);
+ this->SetProgressValue(0);
+
+ // Attempt the write twice. Once with writes being simulated rather than truly committed.
+ // Again with actual writes. If a file memcard has a corrupted page or something which would
+ // cause the conversion to fail, it will fail on the simulated run, with no files committed
+ // to the filesystem yet.
+ for (int i = 0; i < 2; i++)
+ {
+ bool simulateWrites = (i == 0);
+ targetFolderMemoryCard.Open(destPath, config, 0, false, "", simulateWrites);
+
+ size_t address = 0;
+
+ while (address < sourceBuffer.size())
+ {
+ targetFolderMemoryCard.Save(sourceBuffer.data() + address, address, FolderMemoryCard::PageSizeRaw);
+ address += FolderMemoryCard::PageSizeRaw;
+
+ // Only report progress every 16 pages. Substantially speeds up the conversion.
+ if (address % (FolderMemoryCard::PageSizeRaw * 16) == 0)
+ this->SetProgressValue(address + (i * sourceBuffer.size()));
+ }
+
+ targetFolderMemoryCard.Close();
+
+ // If the source file memory card was larger than 8 MB, the raw copy will have also made the superblock of
+ // the destination folder memory card larger than 8 MB. For compatibility, we always want folder memory cards
+ // to report 8 MB, so we'll override that here. Don't do this on the simulated run, only the actual.
+ if (!simulateWrites && sourceBuffer.size() != FolderMemoryCard::TotalSizeRaw)
+ {
+ targetFolderMemoryCard.Open(destPath, config, 0, false, "", simulateWrites);
+ targetFolderMemoryCard.SetSizeInMB(8);
+ targetFolderMemoryCard.Close();
+ }
+
+ this->IncrementProgressValue();
+ }
+
+ return true;
+}
diff --git a/pcsx2-qt/Settings/MemoryCardConvertWorker.h b/pcsx2-qt/Settings/MemoryCardConvertWorker.h
new file mode 100644
index 0000000000..5812c566a2
--- /dev/null
+++ b/pcsx2-qt/Settings/MemoryCardConvertWorker.h
@@ -0,0 +1,31 @@
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "QtProgressCallback.h"
+
+#include "pcsx2/MemoryCardFile.h"
+#include "pcsx2/MemoryCardFolder.h"
+
+class MemoryCardConvertWorker : public QtAsyncProgressThread
+{
+public:
+ MemoryCardConvertWorker(QWidget* parent, MemoryCardType type, MemoryCardFileType fileType, const std::string& srcFileName, const std::string& destFileName);
+ ~MemoryCardConvertWorker();
+
+protected:
+ void runAsync() override;
+
+private:
+ MemoryCardType type;
+ MemoryCardFileType fileType;
+ std::string srcFileName;
+ std::string destFileName;
+
+
+ bool ConvertToFile(const std::string& srcFolderName, const std::string& destFileName, const MemoryCardFileType type);
+ bool ConvertToFolder(const std::string& srcFolderName, const std::string& destFileName, const MemoryCardFileType type);
+};
diff --git a/pcsx2-qt/Settings/MemoryCardSettingsWidget.cpp b/pcsx2-qt/Settings/MemoryCardSettingsWidget.cpp
index 666782c262..ca8cadb009 100644
--- a/pcsx2-qt/Settings/MemoryCardSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/MemoryCardSettingsWidget.cpp
@@ -27,6 +27,7 @@
#include "MemoryCardSettingsWidget.h"
#include "CreateMemoryCardDialog.h"
#include "QtHost.h"
+#include "MemoryCardConvertDialog.h"
#include "QtUtils.h"
#include "SettingWidgetBinder.h"
#include "SettingsDialog.h"
@@ -183,9 +184,13 @@ QString MemoryCardSettingsWidget::getSelectedCard() const
void MemoryCardSettingsWidget::updateCardActions()
{
- const bool hasSelection = !getSelectedCard().isEmpty();
+ QString selectedCard = getSelectedCard();
+ const bool hasSelection = !selectedCard.isEmpty();
+
+ std::optional cardInfo = FileMcd_GetCardInfo(selectedCard.toStdString());
+ bool isPS1 = (cardInfo.has_value() ? cardInfo.value().file_type == MemoryCardFileType::PS1 : false);
- m_ui.convertCard->setEnabled(hasSelection);
+ m_ui.convertCard->setEnabled(hasSelection && !isPS1);
m_ui.duplicateCard->setEnabled(hasSelection);
m_ui.renameCard->setEnabled(hasSelection);
m_ui.deleteCard->setEnabled(hasSelection);
@@ -263,10 +268,14 @@ void MemoryCardSettingsWidget::renameCard()
void MemoryCardSettingsWidget::convertCard()
{
const QString selectedCard(getSelectedCard());
+
if (selectedCard.isEmpty())
return;
- QMessageBox::critical(this, tr("Error"), tr("Not yet implemented."));
+ MemoryCardConvertDialog dialog(QtUtils::GetRootWidget(this), selectedCard);
+
+ if (dialog.IsSetup() && dialog.exec() == QDialog::Accepted)
+ refresh();
}
void MemoryCardSettingsWidget::listContextMenuRequested(const QPoint& pos)
diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj
index 72aeda38de..fcf5f1f65d 100644
--- a/pcsx2-qt/pcsx2-qt.vcxproj
+++ b/pcsx2-qt/pcsx2-qt.vcxproj
@@ -135,6 +135,7 @@
+
@@ -153,6 +154,7 @@
+
@@ -191,6 +193,7 @@
+
@@ -210,6 +213,7 @@
+
@@ -242,6 +246,7 @@
+
@@ -323,6 +328,9 @@
Document
+
+ Document
+
Document
diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters
index 4855247f69..dad20bdbe4 100644
--- a/pcsx2-qt/pcsx2-qt.vcxproj.filters
+++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters
@@ -156,6 +156,9 @@
Settings
+
+ moc
+
moc
@@ -180,6 +183,9 @@
Settings
+
+ Settings
+
Settings
@@ -241,6 +247,9 @@
moc
+
+ Settings
+
@@ -257,6 +266,9 @@
Settings
+
+ Settings
+
@@ -319,6 +331,9 @@
Settings
+
+ Settings
+
Settings
@@ -406,6 +421,9 @@
Settings
+
+ Settings
+
Settings
diff --git a/pcsx2/MemoryCardFolder.cpp b/pcsx2/MemoryCardFolder.cpp
index 44b6a62879..49e9493096 100644
--- a/pcsx2/MemoryCardFolder.cpp
+++ b/pcsx2/MemoryCardFolder.cpp
@@ -290,6 +290,7 @@ void FolderMemoryCard::LoadMemoryCardData(const u32 sizeInClusters, const bool e
MemoryCardFileEntry* const rootDirEntry = &m_fileEntryDict[m_superBlock.data.rootdir_cluster].entries[0];
AddFolder(rootDirEntry, m_folderName, nullptr, enableFiltering, filter);
+
#ifdef DEBUG_WRITE_FOLDER_CARD_IN_MEMORY_TO_FILE_ON_CHANGE
WriteToFile(m_folderName.GetFullPath().RemoveLast() + L"-debug_" + wxDateTime::Now().Format(L"%Y-%m-%d-%H-%M-%S") + L"_load.ps2");
#endif
@@ -2281,6 +2282,12 @@ void MemoryCardFileMetadataReference::GetInternalPath(std::string* fileName) con
FolderMemoryCardAggregator::FolderMemoryCardAggregator()
{
+#ifdef _WIN32
+ // Override Windows' default allowance for open files. Folder memory cards with more than 32 MB of content are likely to contain more than 512 individual files.
+ // Unix platforms seem to use 1024 by default.
+ _setmaxstdio(1024);
+#endif
+
for (uint i = 0; i < TotalCardSlots; ++i)
{
m_cards[i].SetSlot(i);