mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 22:05:38 +00:00
Qt/MemoryCardEditor: Support undeleting files
This commit is contained in:
parent
f7587eaeac
commit
1ecf5cf76a
|
@ -223,15 +223,19 @@ u32 GetFreeBlockCount(const DataArray& data)
|
|||
return count;
|
||||
}
|
||||
|
||||
std::vector<FileInfo> EnumerateFiles(const DataArray& data)
|
||||
std::vector<FileInfo> EnumerateFiles(const DataArray& data, bool include_deleted)
|
||||
{
|
||||
std::vector<FileInfo> files;
|
||||
|
||||
for (u32 dir_frame = 1; dir_frame < FRAMES_PER_BLOCK; dir_frame++)
|
||||
{
|
||||
const DirectoryFrame* df = GetFramePtr<DirectoryFrame>(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<FileInfo> 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<DirectoryFrame>(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<DirectoryFrame>(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<DirectoryFrame>(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<u8> 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<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)
|
||||
{
|
||||
|
@ -560,6 +638,29 @@ static bool ImportSaveWithDirectoryFrame(DataArray* data, const char* filename,
|
|||
return false;
|
||||
}
|
||||
|
||||
const u32 num_blocks = (static_cast<u32>(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<FileInfo> 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<FileInfo> 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<std::vector<u8>> 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<u32>(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<FileInfo> 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());
|
||||
}
|
||||
|
||||
|
|
|
@ -39,16 +39,18 @@ struct FileInfo
|
|||
u32 size;
|
||||
u32 first_block;
|
||||
u32 num_blocks;
|
||||
bool deleted;
|
||||
|
||||
std::vector<IconFrame> icon_frames;
|
||||
};
|
||||
|
||||
bool IsValid(const DataArray& data);
|
||||
u32 GetFreeBlockCount(const DataArray& data);
|
||||
std::vector<FileInfo> EnumerateFiles(const DataArray& data);
|
||||
std::vector<FileInfo> EnumerateFiles(const DataArray& data, bool include_deleted);
|
||||
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 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<u8> file_data);
|
||||
bool ExportSave(DataArray* data, const FileInfo& fi, const char* filename);
|
||||
|
|
|
@ -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<int>::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::Card*, const MemoryCardImage::FileInfo*> 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);
|
||||
|
|
|
@ -29,6 +29,7 @@ private Q_SLOTS:
|
|||
void onCardBSelectionChanged();
|
||||
void doCopyFile();
|
||||
void doDeleteFile();
|
||||
void doUndeleteFile();
|
||||
|
||||
private:
|
||||
struct Card
|
||||
|
|
|
@ -288,6 +288,16 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="undeleteFile">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Undelete File</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="exportFile">
|
||||
<property name="enabled">
|
||||
|
|
Loading…
Reference in a new issue