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);