MemoryCardImage: Implement save file importing and exporting

This commit is contained in:
Albert Liu 2021-03-04 20:50:20 -08:00
parent e939507f42
commit 50a78489f2
4 changed files with 146 additions and 5 deletions

View file

@ -466,4 +466,95 @@ bool ImportCard(DataArray* data, const char* filename)
} }
} }
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 } // namespace MemoryCardImage

View file

@ -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
@ -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

View file

@ -11,6 +11,7 @@ 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)
{ {
@ -85,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)
@ -377,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);
@ -402,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();

View file

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