diff --git a/src/core/memory_card_image.cpp b/src/core/memory_card_image.cpp index 3287fc1ef..b09ec898c 100644 --- a/src/core/memory_card_image.cpp +++ b/src/core/memory_card_image.cpp @@ -223,15 +223,19 @@ u32 GetFreeBlockCount(const DataArray& data) return count; } -std::vector EnumerateFiles(const DataArray& data) +std::vector EnumerateFiles(const DataArray& data, bool include_deleted) { std::vector files; for (u32 dir_frame = 1; dir_frame < FRAMES_PER_BLOCK; dir_frame++) { const DirectoryFrame* df = GetFramePtr(data, 0, dir_frame); - if (df->block_allocation_state != 0x51) + if (df->block_allocation_state != 0x51 && + (!include_deleted || (df->block_allocation_state != 0xA1 && df->block_allocation_state != 0xA2 && + df->block_allocation_state != 0xA3))) + { continue; + } u32 filename_length = 0; while (filename_length < sizeof(df->filename) && df->filename[filename_length] != '\0') @@ -242,6 +246,7 @@ std::vector EnumerateFiles(const DataArray& data) fi.first_block = dir_frame; fi.size = df->file_size; fi.num_blocks = 1; + fi.deleted = (df->block_allocation_state != 0x51); const DirectoryFrame* next_df = df; while (next_df->next_block_number < (NUM_BLOCKS - 1) && fi.num_blocks < FRAMES_PER_BLOCK) @@ -373,7 +378,7 @@ bool WriteFile(DataArray* data, const std::string_view& filename, const std::vec return true; } -bool DeleteFile(DataArray* data, const FileInfo& fi) +bool DeleteFile(DataArray* data, const FileInfo& fi, bool clear_sectors) { Log_InfoPrintf("Deleting '%s' from memory card (%u blocks)", fi.filename.c_str(), fi.num_blocks); @@ -382,13 +387,20 @@ bool DeleteFile(DataArray* data, const FileInfo& fi) { DirectoryFrame* df = GetFramePtr(data, 0, block_number); block_number = ZeroExtend32(df->next_block_number) + 1; - std::memset(df, 0, sizeof(DirectoryFrame)); - if (i == 0) - df->block_allocation_state = 0xA1; - else if (i == (fi.num_blocks - 1)) - df->block_allocation_state = 0xA3; + if (clear_sectors) + { + std::memset(df, 0, sizeof(DirectoryFrame)); + df->block_allocation_state = 0xA0; + } else - df->block_allocation_state = 0xA2; + { + if (i == 0) + df->block_allocation_state = 0xA1; + else if (i == (fi.num_blocks - 1)) + df->block_allocation_state = 0xA3; + else + df->block_allocation_state = 0xA2; + } df->next_block_number = 0xFFFF; UpdateChecksum(df); @@ -397,6 +409,83 @@ bool DeleteFile(DataArray* data, const FileInfo& fi) return true; } +bool UndeleteFile(DataArray* data, const FileInfo& fi) +{ + if (!fi.deleted) + { + Log_ErrorPrintf("File '%s' is not deleted", fi.filename.c_str()); + return false; + } + + Log_InfoPrintf("Undeleting '%s' from memory card (%u blocks)", fi.filename.c_str(), fi.num_blocks); + + // check that all blocks are present first + u32 block_number = fi.first_block; + for (u32 i = 0; i < fi.num_blocks && (block_number > 0 && block_number < NUM_BLOCKS); i++) + { + DirectoryFrame* df = GetFramePtr(data, 0, block_number); + block_number = ZeroExtend32(df->next_block_number) + 1; + + if (i == 0) + { + if (df->block_allocation_state != 0xA1) + { + Log_ErrorPrintf("Incorrect block state for %u, expected 0xA1 got 0x%02X", df->block_allocation_state); + return false; + } + } + else if (i == (fi.num_blocks - 1)) + { + if (df->block_allocation_state != 0xA3) + { + Log_ErrorPrintf("Incorrect block state for %u, expected 0xA3 got 0x%02X", df->block_allocation_state); + return false; + } + } + else + { + if (df->block_allocation_state != 0xA2) + { + Log_WarningPrintf("Incorrect block state for %u, expected 0xA2 got 0x%02X", df->block_allocation_state); + return false; + } + } + } + + block_number = fi.first_block; + for (u32 i = 0; i < fi.num_blocks && (block_number > 0 && block_number < NUM_BLOCKS); i++) + { + DirectoryFrame* df = GetFramePtr(data, 0, block_number); + block_number = ZeroExtend32(df->next_block_number) + 1; + + if (i == 0) + { + if (df->block_allocation_state != 0xA1) + Log_WarningPrintf("Incorrect block state for %u, expected 0xA1 got 0x%02X", df->block_allocation_state); + + df->block_allocation_state = 0x51; + } + else if (i == (fi.num_blocks - 1)) + { + if (df->block_allocation_state != 0xA3) + Log_WarningPrintf("Incorrect block state for %u, expected 0xA3 got 0x%02X", df->block_allocation_state); + + df->block_allocation_state = 0x53; + } + else + { + if (df->block_allocation_state != 0xA2) + Log_WarningPrintf("Incorrect block state for %u, expected 0xA2 got 0x%02X", df->block_allocation_state); + + df->block_allocation_state = 0x52; + } + + UpdateChecksum(df); + } + + return true; +} + static bool ImportCardMCD(DataArray* data, const char* filename, std::vector file_data) { if (file_data.size() != DATA_SIZE) @@ -542,17 +631,6 @@ static bool ImportSaveWithDirectoryFrame(DataArray* data, const char* filename, return false; } - // Make sure there isn't already a save with the same name - std::vector 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 blocks = std::vector(static_cast(df.file_size)); if (stream->Read(blocks.data(), df.file_size) != df.file_size) { @@ -560,6 +638,29 @@ static bool ImportSaveWithDirectoryFrame(DataArray* data, const char* filename, return false; } + const u32 num_blocks = (static_cast(blocks.size()) + (BLOCK_SIZE - 1)) / BLOCK_SIZE; + if (GetFreeBlockCount(*data) < num_blocks) + { + Log_ErrorPrintf("Failed to write file to memory card: insufficient free blocks"); + return false; + } + + // Make sure there isn't already a save with the same name + std::vector fileinfos = EnumerateFiles(*data, true); + for (const FileInfo& fi : fileinfos) + { + if (fi.filename.compare(0, sizeof(df.filename), df.filename) == 0) + { + if (!fi.deleted) + { + Log_ErrorPrintf("Save file with the same name '%s' already exists in memory card", fi.filename.c_str()); + return false; + } + + DeleteFile(data, fi, true); + } + } + return WriteFile(data, df.filename, blocks); } @@ -575,17 +676,6 @@ static bool ImportRawSave(DataArray* data, const char* filename, const FILESYSTE if (save_name.length() > DirectoryFrame::FILE_NAME_LENGTH) save_name.erase(DirectoryFrame::FILE_NAME_LENGTH); - // Make sure there isn't already a save with the same name - std::vector fileinfos = EnumerateFiles(*data); - for (const FileInfo& fi : fileinfos) - { - if (fi.filename.compare(save_name) == 0) - { - Log_ErrorPrintf("Save file with the same name (%s) already exists in memory card", save_name.c_str()); - return false; - } - } - std::optional> blocks = FileSystem::ReadBinaryFile(filename); if (!blocks.has_value()) { @@ -593,6 +683,29 @@ static bool ImportRawSave(DataArray* data, const char* filename, const FILESYSTE return false; } + const u32 num_blocks = (static_cast(blocks->size()) + (BLOCK_SIZE - 1)) / BLOCK_SIZE; + if (GetFreeBlockCount(*data) < num_blocks) + { + Log_ErrorPrintf("Failed to write file to memory card: insufficient free blocks"); + return false; + } + + // Make sure there isn't already a save with the same name + std::vector fileinfos = EnumerateFiles(*data, true); + for (const FileInfo& fi : fileinfos) + { + if (fi.filename.compare(save_name) == 0) + { + if (!fi.deleted) + { + Log_ErrorPrintf("Save file with the same name '%s' already exists in memory card", fi.filename.c_str()); + return false; + } + + DeleteFile(data, fi, true); + } + } + return WriteFile(data, save_name, blocks.value()); } diff --git a/src/core/memory_card_image.h b/src/core/memory_card_image.h index d047b26a2..4cee3cbb5 100644 --- a/src/core/memory_card_image.h +++ b/src/core/memory_card_image.h @@ -39,16 +39,18 @@ struct FileInfo u32 size; u32 first_block; u32 num_blocks; + bool deleted; std::vector icon_frames; }; bool IsValid(const DataArray& data); u32 GetFreeBlockCount(const DataArray& data); -std::vector EnumerateFiles(const DataArray& data); +std::vector EnumerateFiles(const DataArray& data, bool include_deleted); bool ReadFile(const DataArray& data, const FileInfo& fi, std::vector* buffer); bool WriteFile(DataArray* data, const std::string_view& filename, const std::vector& buffer); -bool DeleteFile(DataArray* data, const FileInfo& fi); +bool DeleteFile(DataArray* data, const FileInfo& fi, bool clear_sectors); +bool UndeleteFile(DataArray* data, const FileInfo& fi); bool ImportCard(DataArray* data, const char* filename); bool ImportCard(DataArray* data, const char* filename, std::vector file_data); bool ExportSave(DataArray* data, const FileInfo& fi, const char* filename); diff --git a/src/duckstation-qt/memorycardeditordialog.cpp b/src/duckstation-qt/memorycardeditordialog.cpp index a09cf23cd..8b9dad13e 100644 --- a/src/duckstation-qt/memorycardeditordialog.cpp +++ b/src/duckstation-qt/memorycardeditordialog.cpp @@ -105,6 +105,7 @@ void MemoryCardEditorDialog::connectUi() connect(m_ui.moveLeft, &QPushButton::clicked, this, &MemoryCardEditorDialog::doCopyFile); connect(m_ui.moveRight, &QPushButton::clicked, this, &MemoryCardEditorDialog::doCopyFile); connect(m_ui.deleteFile, &QPushButton::clicked, this, &MemoryCardEditorDialog::doDeleteFile); + connect(m_ui.undeleteFile, &QPushButton::clicked, this, &MemoryCardEditorDialog::doUndeleteFile); connect(m_ui.cardAPath, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) { loadCardFromComboBox(&m_card_a, index); }); @@ -219,11 +220,20 @@ bool MemoryCardEditorDialog::loadCard(const QString& filename, Card* card) return true; } +static void setCardTableItemProperties(QTableWidgetItem* item, const MemoryCardImage::FileInfo& fi) +{ + if (fi.deleted) + { + item->setBackground(Qt::darkRed); + item->setForeground(Qt::white); + } +} + void MemoryCardEditorDialog::updateCardTable(Card* card) { card->table->setRowCount(0); - card->files = MemoryCardImage::EnumerateFiles(card->data); + card->files = MemoryCardImage::EnumerateFiles(card->data, true); for (const MemoryCardImage::FileInfo& fi : card->files) { const int row = card->table->rowCount(); @@ -235,13 +245,26 @@ void MemoryCardEditorDialog::updateCardTable(Card* card) MemoryCardImage::ICON_HEIGHT, QImage::Format_RGBA8888); QTableWidgetItem* icon = new QTableWidgetItem(); + setCardTableItemProperties(icon, fi); icon->setIcon(QIcon(QPixmap::fromImage(image))); card->table->setItem(row, 0, icon); } - card->table->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(fi.title))); - card->table->setItem(row, 2, new QTableWidgetItem(QString::fromStdString(fi.filename))); - card->table->setItem(row, 3, new QTableWidgetItem(QString::number(fi.num_blocks))); + QString title_str(QString::fromStdString(fi.title)); + if (fi.deleted) + title_str += tr(" (Deleted)"); + + QTableWidgetItem* item = new QTableWidgetItem(title_str); + setCardTableItemProperties(item, fi); + card->table->setItem(row, 1, item); + + item = new QTableWidgetItem(QString::fromStdString(fi.filename)); + setCardTableItemProperties(item, fi); + card->table->setItem(row, 2, item); + + item = new QTableWidgetItem(QString::number(fi.num_blocks)); + setCardTableItemProperties(item, fi); + card->table->setItem(row, 3, item); } } @@ -390,9 +413,9 @@ void MemoryCardEditorDialog::doCopyFile() } clearSelection(); + setCardDirty(dst); updateCardTable(dst); updateCardBlocksFree(dst); - setCardDirty(dst); updateButtonState(); } @@ -402,16 +425,38 @@ void MemoryCardEditorDialog::doDeleteFile() if (!fi) return; - if (!MemoryCardImage::DeleteFile(&card->data, *fi)) + if (!MemoryCardImage::DeleteFile(&card->data, *fi, fi->deleted)) { QMessageBox::critical(this, tr("Error"), tr("Failed to delete file %1").arg(QString::fromStdString(fi->filename))); return; } clearSelection(); + setCardDirty(card); updateCardTable(card); updateCardBlocksFree(card); + updateButtonState(); +} + +void MemoryCardEditorDialog::doUndeleteFile() +{ + const auto [card, fi] = getSelectedFile(); + if (!fi) + return; + + if (!MemoryCardImage::UndeleteFile(&card->data, *fi)) + { + QMessageBox::critical( + this, tr("Error"), + tr("Failed to undelete file %1. The file may have been partially overwritten by another save.") + .arg(QString::fromStdString(fi->filename))); + return; + } + + clearSelection(); setCardDirty(card); + updateCardTable(card); + updateCardBlocksFree(card); updateButtonState(); } @@ -455,9 +500,9 @@ void MemoryCardEditorDialog::importCard(Card* card) clearSelection(); card->data = *temp; + setCardDirty(card); updateCardTable(card); updateCardBlocksFree(card); - setCardDirty(card); updateButtonState(); } @@ -478,9 +523,9 @@ void MemoryCardEditorDialog::formatCard(Card* card) MemoryCardImage::Format(&card->data); + setCardDirty(card); updateCardTable(card); updateCardBlocksFree(card); - setCardDirty(card); updateButtonState(); } @@ -500,9 +545,9 @@ void MemoryCardEditorDialog::importSaveFile(Card* card) return; } + setCardDirty(card); updateCardTable(card); updateCardBlocksFree(card); - setCardDirty(card); } std::tuple MemoryCardEditorDialog::getSelectedFile() @@ -530,10 +575,12 @@ void MemoryCardEditorDialog::updateButtonState() const auto [selected_card, selected_file] = getSelectedFile(); const bool is_card_b = (selected_card == &m_card_b); const bool has_selection = (selected_file != nullptr); + const bool is_deleted = (selected_file != nullptr && selected_file->deleted); const bool card_a_present = !m_card_a.filename.empty(); const bool card_b_present = !m_card_b.filename.empty(); const bool both_cards_present = card_a_present && card_b_present; m_ui.deleteFile->setEnabled(has_selection); + m_ui.undeleteFile->setEnabled(is_deleted); m_ui.exportFile->setEnabled(has_selection); m_ui.moveLeft->setEnabled(both_cards_present && has_selection && is_card_b); m_ui.moveRight->setEnabled(both_cards_present && has_selection && !is_card_b); diff --git a/src/duckstation-qt/memorycardeditordialog.h b/src/duckstation-qt/memorycardeditordialog.h index e18f69d9d..e5435e7c9 100644 --- a/src/duckstation-qt/memorycardeditordialog.h +++ b/src/duckstation-qt/memorycardeditordialog.h @@ -29,6 +29,7 @@ private Q_SLOTS: void onCardBSelectionChanged(); void doCopyFile(); void doDeleteFile(); + void doUndeleteFile(); private: struct Card diff --git a/src/duckstation-qt/memorycardeditordialog.ui b/src/duckstation-qt/memorycardeditordialog.ui index 9c7d2bb7b..74f2425d2 100644 --- a/src/duckstation-qt/memorycardeditordialog.ui +++ b/src/duckstation-qt/memorycardeditordialog.ui @@ -288,6 +288,16 @@ + + + + false + + + Undelete File + + +