mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-02-18 11:55:38 +00:00
Merge pull request #1743 from ggrtk/memcard-editor
Qt/MemoryCardEditor: Implement missing functionality and clean up some stuff
This commit is contained in:
commit
b68585acc7
|
@ -466,4 +466,95 @@ bool ImportCard(DataArray* data, const char* filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace MemoryCardImage
|
bool ExportSave(DataArray* data, const FileInfo& fi, const char* filename)
|
||||||
|
{
|
||||||
|
std::unique_ptr<ByteStream> stream =
|
||||||
|
FileSystem::OpenFile(filename, BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_TRUNCATE | BYTESTREAM_OPEN_WRITE |
|
||||||
|
BYTESTREAM_OPEN_ATOMIC_UPDATE | BYTESTREAM_OPEN_STREAMED);
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to open '%s' for writing.", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryFrame* df_ptr = GetFramePtr<DirectoryFrame>(data, 0, fi.first_block);
|
||||||
|
std::vector<u8> header = std::vector<u8>(static_cast<size_t>(FRAME_SIZE));
|
||||||
|
std::memcpy(header.data(), df_ptr, sizeof(*df_ptr));
|
||||||
|
|
||||||
|
std::vector<u8> blocks;
|
||||||
|
if (!ReadFile(*data, fi, &blocks))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to read save blocks from memory card data");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream->Write(header.data(), static_cast<u32>(header.size())) ||
|
||||||
|
!stream->Write(blocks.data(), static_cast<u32>(blocks.size())) || !stream->Commit())
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to write exported save to '%s'", filename);
|
||||||
|
stream->Discard();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImportSave(DataArray* data, const char* filename)
|
||||||
|
{
|
||||||
|
FILESYSTEM_STAT_DATA sd;
|
||||||
|
if (!FileSystem::StatFile(filename, &sd))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to stat file '%s'", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the size of the actual file is valid
|
||||||
|
if (sd.Size <= FRAME_SIZE || (sd.Size - FRAME_SIZE) % BLOCK_SIZE != 0u || (sd.Size - FRAME_SIZE) / BLOCK_SIZE > 15u)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Invalid size for save file '%s'", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ByteStream> stream = FileSystem::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED);
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to open '%s' for reading", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryFrame df;
|
||||||
|
if (stream->Read(&df, FRAME_SIZE) != FRAME_SIZE)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to read directory frame from '%s'", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the size reported by the directory frame is valid
|
||||||
|
if (df.file_size < BLOCK_SIZE || df.file_size % BLOCK_SIZE != 0 || df.file_size / BLOCK_SIZE > 15u)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Invalid size (%u bytes) reported by directory frame", df.file_size);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure there isn't already a save with the same name
|
||||||
|
std::vector<FileInfo> fileinfos = EnumerateFiles(*data);
|
||||||
|
for (const FileInfo& fi : fileinfos)
|
||||||
|
{
|
||||||
|
if (fi.filename.compare(0, sizeof(df.filename), df.filename) == 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Save file with the same name already exists in memory card");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> blocks = std::vector<u8>(static_cast<size_t>(df.file_size));
|
||||||
|
if (stream->Read(blocks.data(), df.file_size) != df.file_size)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to read block bytes from '%s'", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return WriteFile(data, df.filename, blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MemoryCardImage
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace MemoryCardImage
|
namespace MemoryCardImage {
|
||||||
{
|
|
||||||
enum : u32
|
enum : u32
|
||||||
{
|
{
|
||||||
DATA_SIZE = 128 * 1024, // 1mbit
|
DATA_SIZE = 128 * 1024, // 1mbit
|
||||||
|
@ -17,7 +16,7 @@ enum : u32
|
||||||
FRAMES_PER_BLOCK = BLOCK_SIZE / FRAME_SIZE,
|
FRAMES_PER_BLOCK = BLOCK_SIZE / FRAME_SIZE,
|
||||||
NUM_BLOCKS = DATA_SIZE / BLOCK_SIZE,
|
NUM_BLOCKS = DATA_SIZE / BLOCK_SIZE,
|
||||||
NUM_FRAMES = DATA_SIZE / FRAME_SIZE,
|
NUM_FRAMES = DATA_SIZE / FRAME_SIZE,
|
||||||
ICON_WIDTH =16,
|
ICON_WIDTH = 16,
|
||||||
ICON_HEIGHT = 16
|
ICON_HEIGHT = 16
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,4 +49,6 @@ bool ReadFile(const DataArray& data, const FileInfo& fi, std::vector<u8>* buffer
|
||||||
bool WriteFile(DataArray* data, const std::string_view& filename, const std::vector<u8>& buffer);
|
bool WriteFile(DataArray* data, const std::string_view& filename, const std::vector<u8>& buffer);
|
||||||
bool DeleteFile(DataArray* data, const FileInfo& fi);
|
bool DeleteFile(DataArray* data, const FileInfo& fi);
|
||||||
bool ImportCard(DataArray* data, const char* filename);
|
bool ImportCard(DataArray* data, const char* filename);
|
||||||
}
|
bool ExportSave(DataArray* data, const FileInfo& fi, const char* filename);
|
||||||
|
bool ImportSave(DataArray* data, const char* filename);
|
||||||
|
} // namespace MemoryCardImage
|
||||||
|
|
|
@ -11,10 +11,13 @@ static constexpr char MEMORY_CARD_IMAGE_FILTER[] =
|
||||||
QT_TRANSLATE_NOOP("MemoryCardEditorDialog", "All Memory Card Types (*.mcd *.mcr *.mc)");
|
QT_TRANSLATE_NOOP("MemoryCardEditorDialog", "All Memory Card Types (*.mcd *.mcr *.mc)");
|
||||||
static constexpr char MEMORY_CARD_IMPORT_FILTER[] =
|
static constexpr char MEMORY_CARD_IMPORT_FILTER[] =
|
||||||
QT_TRANSLATE_NOOP("MemoryCardEditorDialog", "All Importable Memory Card Types (*.mcd *.mcr *.mc *.gme)");
|
QT_TRANSLATE_NOOP("MemoryCardEditorDialog", "All Importable Memory Card Types (*.mcd *.mcr *.mc *.gme)");
|
||||||
|
static constexpr char SINGLE_SAVEFILE_FILTER[] = TRANSLATABLE("MemoryCardEditorDialog", "Single Save Files (*.mcs)");
|
||||||
|
|
||||||
MemoryCardEditorDialog::MemoryCardEditorDialog(QWidget* parent) : QDialog(parent)
|
MemoryCardEditorDialog::MemoryCardEditorDialog(QWidget* parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
m_ui.setupUi(this);
|
m_ui.setupUi(this);
|
||||||
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||||
|
|
||||||
m_card_a.path_cb = m_ui.cardAPath;
|
m_card_a.path_cb = m_ui.cardAPath;
|
||||||
m_card_a.table = m_ui.cardA;
|
m_card_a.table = m_ui.cardA;
|
||||||
m_card_a.blocks_free_label = m_ui.cardAUsage;
|
m_card_a.blocks_free_label = m_ui.cardAUsage;
|
||||||
|
@ -53,8 +56,8 @@ bool MemoryCardEditorDialog::setCardB(const QString& path)
|
||||||
|
|
||||||
void MemoryCardEditorDialog::resizeEvent(QResizeEvent* ev)
|
void MemoryCardEditorDialog::resizeEvent(QResizeEvent* ev)
|
||||||
{
|
{
|
||||||
QtUtils::ResizeColumnsForTableView(m_card_a.table, {32, -1, 100, 45});
|
QtUtils::ResizeColumnsForTableView(m_card_a.table, {32, -1, 155, 45});
|
||||||
QtUtils::ResizeColumnsForTableView(m_card_b.table, {32, -1, 100, 45});
|
QtUtils::ResizeColumnsForTableView(m_card_b.table, {32, -1, 155, 45});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryCardEditorDialog::closeEvent(QCloseEvent* ev)
|
void MemoryCardEditorDialog::closeEvent(QCloseEvent* ev)
|
||||||
|
@ -83,6 +86,9 @@ void MemoryCardEditorDialog::connectUi()
|
||||||
connect(m_ui.saveCardB, &QPushButton::clicked, [this]() { saveCard(&m_card_b); });
|
connect(m_ui.saveCardB, &QPushButton::clicked, [this]() { saveCard(&m_card_b); });
|
||||||
connect(m_ui.importCardA, &QPushButton::clicked, [this]() { importCard(&m_card_a); });
|
connect(m_ui.importCardA, &QPushButton::clicked, [this]() { importCard(&m_card_a); });
|
||||||
connect(m_ui.importCardB, &QPushButton::clicked, [this]() { importCard(&m_card_b); });
|
connect(m_ui.importCardB, &QPushButton::clicked, [this]() { importCard(&m_card_b); });
|
||||||
|
connect(m_ui.exportFile, &QPushButton::clicked, this, &MemoryCardEditorDialog::doExportSaveFile);
|
||||||
|
connect(m_ui.importFileToCardA, &QPushButton::clicked, [this]() { importSaveFile(&m_card_a); });
|
||||||
|
connect(m_ui.importFileToCardB, &QPushButton::clicked, [this]() { importSaveFile(&m_card_b); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryCardEditorDialog::populateComboBox(QComboBox* cb)
|
void MemoryCardEditorDialog::populateComboBox(QComboBox* cb)
|
||||||
|
@ -92,7 +98,6 @@ void MemoryCardEditorDialog::populateComboBox(QComboBox* cb)
|
||||||
cb->clear();
|
cb->clear();
|
||||||
|
|
||||||
cb->addItem(QString());
|
cb->addItem(QString());
|
||||||
cb->addItem(tr("Browse..."));
|
|
||||||
|
|
||||||
const std::string base_path(g_host_interface->GetUserDirectoryRelativePath("memcards"));
|
const std::string base_path(g_host_interface->GetUserDirectoryRelativePath("memcards"));
|
||||||
FileSystem::FindResultsArray results;
|
FileSystem::FindResultsArray results;
|
||||||
|
@ -111,29 +116,7 @@ void MemoryCardEditorDialog::populateComboBox(QComboBox* cb)
|
||||||
|
|
||||||
void MemoryCardEditorDialog::loadCardFromComboBox(Card* card, int index)
|
void MemoryCardEditorDialog::loadCardFromComboBox(Card* card, int index)
|
||||||
{
|
{
|
||||||
QString filename;
|
loadCard(card->path_cb->itemData(index).toString(), card);
|
||||||
if (index == 1)
|
|
||||||
{
|
|
||||||
filename = QDir::toNativeSeparators(
|
|
||||||
QFileDialog::getOpenFileName(this, tr("Select Memory Card"), QString(), tr(MEMORY_CARD_IMAGE_FILTER)));
|
|
||||||
if (!filename.isEmpty())
|
|
||||||
{
|
|
||||||
// add to combo box
|
|
||||||
QFileInfo file(filename);
|
|
||||||
QSignalBlocker sb(card->path_cb);
|
|
||||||
card->path_cb->addItem(file.baseName(), QVariant(filename));
|
|
||||||
card->path_cb->setCurrentIndex(card->path_cb->count() - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
filename = card->path_cb->itemData(index).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filename.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
loadCard(filename, card);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryCardEditorDialog::onCardASelectionChanged()
|
void MemoryCardEditorDialog::onCardASelectionChanged()
|
||||||
|
@ -182,6 +165,12 @@ bool MemoryCardEditorDialog::loadCard(const QString& filename, Card* card)
|
||||||
|
|
||||||
card->filename.clear();
|
card->filename.clear();
|
||||||
|
|
||||||
|
if (filename.isEmpty())
|
||||||
|
{
|
||||||
|
updateButtonState();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::string filename_str = filename.toStdString();
|
std::string filename_str = filename.toStdString();
|
||||||
if (!MemoryCardImage::LoadFromFile(&card->data, filename_str.c_str()))
|
if (!MemoryCardImage::LoadFromFile(&card->data, filename_str.c_str()))
|
||||||
{
|
{
|
||||||
|
@ -331,6 +320,19 @@ void MemoryCardEditorDialog::doCopyFile()
|
||||||
|
|
||||||
Card* dst = (src == &m_card_a) ? &m_card_b : &m_card_a;
|
Card* dst = (src == &m_card_a) ? &m_card_b : &m_card_a;
|
||||||
|
|
||||||
|
for (const MemoryCardImage::FileInfo& dst_fi : dst->files)
|
||||||
|
{
|
||||||
|
if (dst_fi.filename == fi->filename)
|
||||||
|
{
|
||||||
|
QMessageBox::critical(
|
||||||
|
this, tr("Error"),
|
||||||
|
tr("Destination memory card already contains a save file with the same name (%1) as the one you are attempting "
|
||||||
|
"to copy. Please delete this file from the destination memory card before copying.")
|
||||||
|
.arg(QString(fi->filename.c_str())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (dst->blocks_free < fi->num_blocks)
|
if (dst->blocks_free < fi->num_blocks)
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this, tr("Error"),
|
QMessageBox::critical(this, tr("Error"),
|
||||||
|
@ -379,6 +381,27 @@ void MemoryCardEditorDialog::doDeleteFile()
|
||||||
updateButtonState();
|
updateButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MemoryCardEditorDialog::doExportSaveFile()
|
||||||
|
{
|
||||||
|
QString filename = QDir::toNativeSeparators(
|
||||||
|
QFileDialog::getSaveFileName(this, tr("Select Single Savefile"), QString(), tr(SINGLE_SAVEFILE_FILTER)));
|
||||||
|
|
||||||
|
if (filename.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto [card, fi] = getSelectedFile();
|
||||||
|
if (!fi)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!MemoryCardImage::ExportSave(&card->data, *fi, filename.toStdString().c_str()))
|
||||||
|
{
|
||||||
|
QMessageBox::critical(
|
||||||
|
this, tr("Error"),
|
||||||
|
tr("Failed to export save file %1. Check the log for more details.").arg(QString::fromStdString(fi->filename)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MemoryCardEditorDialog::importCard(Card* card)
|
void MemoryCardEditorDialog::importCard(Card* card)
|
||||||
{
|
{
|
||||||
promptForSave(card);
|
promptForSave(card);
|
||||||
|
@ -404,6 +427,27 @@ void MemoryCardEditorDialog::importCard(Card* card)
|
||||||
updateButtonState();
|
updateButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MemoryCardEditorDialog::importSaveFile(Card* card)
|
||||||
|
{
|
||||||
|
QString filename =
|
||||||
|
QFileDialog::getOpenFileName(this, tr("Select Import Save File"), QString(), tr(SINGLE_SAVEFILE_FILTER));
|
||||||
|
|
||||||
|
if (filename.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!MemoryCardImage::ImportSave(&card->data, filename.toStdString().c_str()))
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, tr("Error"),
|
||||||
|
tr("Failed to import save. Check if there is enough room on the memory card or if an "
|
||||||
|
"existing save with the same name already exists."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCardTable(card);
|
||||||
|
updateCardBlocksFree(card);
|
||||||
|
setCardDirty(card);
|
||||||
|
}
|
||||||
|
|
||||||
std::tuple<MemoryCardEditorDialog::Card*, const MemoryCardImage::FileInfo*> MemoryCardEditorDialog::getSelectedFile()
|
std::tuple<MemoryCardEditorDialog::Card*, const MemoryCardImage::FileInfo*> MemoryCardEditorDialog::getSelectedFile()
|
||||||
{
|
{
|
||||||
QList<QTableWidgetSelectionRange> sel = m_card_a.table->selectedRanges();
|
QList<QTableWidgetSelectionRange> sel = m_card_a.table->selectedRanges();
|
||||||
|
@ -438,4 +482,6 @@ void MemoryCardEditorDialog::updateButtonState()
|
||||||
m_ui.moveRight->setEnabled(both_cards_present && has_selection && !is_card_b);
|
m_ui.moveRight->setEnabled(both_cards_present && has_selection && !is_card_b);
|
||||||
m_ui.importCardA->setEnabled(card_a_present);
|
m_ui.importCardA->setEnabled(card_a_present);
|
||||||
m_ui.importCardB->setEnabled(card_b_present);
|
m_ui.importCardB->setEnabled(card_b_present);
|
||||||
|
m_ui.importFileToCardA->setEnabled(card_a_present);
|
||||||
|
m_ui.importFileToCardB->setEnabled(card_b_present);
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,9 @@ private:
|
||||||
void promptForSave(Card* card);
|
void promptForSave(Card* card);
|
||||||
void importCard(Card* card);
|
void importCard(Card* card);
|
||||||
|
|
||||||
|
void doExportSaveFile();
|
||||||
|
void importSaveFile(Card* card);
|
||||||
|
|
||||||
std::tuple<Card*, const MemoryCardImage::FileInfo*> getSelectedFile();
|
std::tuple<Card*, const MemoryCardImage::FileInfo*> getSelectedFile();
|
||||||
void updateButtonState();
|
void updateButtonState();
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>846</width>
|
<width>889</width>
|
||||||
<height>515</height>
|
<height>515</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
@ -101,7 +101,7 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="cardBUsage">
|
<widget class="QLabel" name="cardBUsage">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>0 blocks used</string>
|
<string notr="true"/>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="cardAUsage">
|
<widget class="QLabel" name="cardAUsage">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>0 blocks used</string>
|
<string notr="true"/>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
Loading…
Reference in a new issue