diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 101259e035..88bc7cacee 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -24,6 +24,8 @@ #include "svnrev.h" +bool RemoveDirectory( const wxString& dirname ); + FolderMemoryCard::FolderMemoryCard() { m_slot = 0; m_isEnabled = false; @@ -805,16 +807,22 @@ s32 FolderMemoryCard::Save( const u8 *src, u32 adr, int size ) { void FolderMemoryCard::NextFrame() { if ( m_framesUntilFlush > 0 && --m_framesUntilFlush == 0 ) { Flush(); - m_lastAccessedFile.Close(); } } void FolderMemoryCard::Flush() { + m_lastAccessedFile.Close(); if ( m_cache.empty() ) { return; } Console.WriteLn( L"(FolderMcd) Writing data for slot %u to file system...", m_slot ); const u64 timeFlushStart = wxGetLocalTimeMillis().GetValue(); + // Keep a copy of the old file entries so we can figure out which files and directories, if any, have been deleted from the memory card. + std::vector oldFileEntryTree; + if ( IsFormatted() ) { + CopyEntryDictIntoTree( &oldFileEntryTree, m_superBlock.data.rootdir_cluster, m_fileEntryDict[m_superBlock.data.rootdir_cluster].entries[0].entry.data.length ); + } + // first write the superblock if necessary FlushBlock( 0 ); if ( !IsFormatted() ) { return; } @@ -851,6 +859,9 @@ void FolderMemoryCard::Flush() { // then all directory and file entries FlushFileEntries(); + // Now we have the new file system, compare it to the old one and "delete" any files that were in it before but aren't anymore. + FlushDeletedFiles( oldFileEntryTree ); + // and finally, flush everything that hasn't been flushed yet for ( uint i = 0; i < pageCount; ++i ) { FlushPage( i ); @@ -944,6 +955,46 @@ void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remaini } } +void FolderMemoryCard::FlushDeletedFiles( const std::vector& oldFileEntries ) { + const u32 newRootDirCluster = m_superBlock.data.rootdir_cluster; + const u32 newFileCount = m_fileEntryDict[newRootDirCluster].entries[0].entry.data.length; + wxString path = L""; + FlushDeletedFiles( oldFileEntries, newRootDirCluster, newFileCount, path ); +} + +void FolderMemoryCard::FlushDeletedFiles( const std::vector& oldFileEntries, const u32 newCluster, const u32 newFileCount, const wxString& dirPath ) { + // go through all file entires of the current directory of the old data + for ( auto it = oldFileEntries.cbegin(); it != oldFileEntries.cend(); ++it ) { + const MemoryCardFileEntry* entry = &it->entry; + if ( entry->IsValid() && entry->IsUsed() && !entry->IsDotDir() ) { + // check if an equivalent entry exists in m_fileEntryDict + const MemoryCardFileEntry* newEntry = FindEquivalent( entry, newCluster, newFileCount ); + if ( newEntry == nullptr ) { + // file/dir doesn't exist anymore, remove! + char cleanName[sizeof( entry->entry.data.name )]; + memcpy( cleanName, (const char*)entry->entry.data.name, sizeof( cleanName ) ); + FileAccessHelper::CleanMemcardFilename( cleanName ); + const wxString fileName = wxString::FromAscii( cleanName ); + const wxString filePath = m_folderName.GetFullPath() + dirPath + L"/" + fileName; + const wxString newFilePath = m_folderName.GetFullPath() + dirPath + L"/_pcsx2_deleted_" + fileName; + if ( wxFileName::DirExists( newFilePath ) ) { + // wxRenameFile doesn't overwrite directories, so we have to remove the old one first + RemoveDirectory( newFilePath ); + } + wxRenameFile( filePath, newFilePath ); + } else if ( entry->IsDir() ) { + // still exists and is a directory, recursive call for subdir + char cleanName[sizeof( entry->entry.data.name )]; + memcpy( cleanName, (const char*)entry->entry.data.name, sizeof( cleanName ) ); + FileAccessHelper::CleanMemcardFilename( cleanName ); + const wxString subDirName = wxString::FromAscii( cleanName ); + const wxString subDirPath = dirPath + L"/" + subDirName; + FlushDeletedFiles( it->subdir, newEntry->entry.data.cluster, newEntry->entry.data.length, subDirPath ); + } + } + } +} + s32 FolderMemoryCard::WriteWithoutCache( const u8 *src, u32 adr, int size ) { const u32 block = adr / BlockSizeRaw; const u32 cluster = adr / ClusterSizeRaw; @@ -1031,6 +1082,54 @@ bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { return false; } +void FolderMemoryCard::CopyEntryDictIntoTree( std::vector* fileEntryTree, const u32 cluster, const u32 fileCount ) { + const MemoryCardFileEntryCluster* entryCluster = &m_fileEntryDict[cluster]; + u32 fileCluster = cluster; + + for ( size_t i = 0; i < fileCount; ++i ) { + const MemoryCardFileEntry* entry = &entryCluster->entries[i % 2]; + + if ( entry->IsValid() && entry->IsUsed() ) { + fileEntryTree->emplace_back( *entry ); + + if ( entry->IsDir() && !entry->IsDotDir() ) { + MemoryCardFileEntryTreeNode* treeEntry = &fileEntryTree->back(); + CopyEntryDictIntoTree( &treeEntry->subdir, entry->entry.data.cluster, entry->entry.data.length ); + } + } + + if ( i % 2 == 1 ) { + fileCluster = m_fat.data[0][0][fileCluster] & 0x7FFFFFFFu; + if ( fileCluster == 0x7FFFFFFFu ) { return; } + entryCluster = &m_fileEntryDict[fileCluster]; + } + } +} + +const MemoryCardFileEntry* FolderMemoryCard::FindEquivalent( const MemoryCardFileEntry* searchEntry, const u32 cluster, const u32 fileCount ) { + const MemoryCardFileEntryCluster* entryCluster = &m_fileEntryDict[cluster]; + u32 fileCluster = cluster; + + for ( size_t i = 0; i < fileCount; ++i ) { + const MemoryCardFileEntry* entry = &entryCluster->entries[i % 2]; + + if ( entry->IsValid() && entry->IsUsed() ) { + if ( entry->IsFile() == searchEntry->IsFile() && entry->IsDir() == searchEntry->IsDir() + && strncmp( (const char*)searchEntry->entry.data.name, (const char*)entry->entry.data.name, sizeof( entry->entry.data.name ) ) == 0 ) { + return entry; + } + } + + if ( i % 2 == 1 ) { + fileCluster = m_fat.data[0][0][fileCluster] & 0x7FFFFFFFu; + if ( fileCluster == 0x7FFFFFFFu ) { return nullptr; } + entryCluster = &m_fileEntryDict[fileCluster]; + } + } + + return nullptr; +} + s32 FolderMemoryCard::EraseBlock( u32 adr ) { const u32 block = adr / BlockSizeRaw; diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index a9e744f282..906d376e03 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -19,6 +19,7 @@ #include #include #include +#include #include "PluginCallbacks.h" #include "AppConfig.h" @@ -152,6 +153,13 @@ struct MemoryCardPage { }; #pragma pack(pop) +struct MemoryCardFileEntryTreeNode { + MemoryCardFileEntry entry; + std::vector subdir; + + MemoryCardFileEntryTreeNode( const MemoryCardFileEntry& entry ) : entry(entry) {} +}; + // -------------------------------------------------------------------------------------- // MemoryCardFileMetadataReference // -------------------------------------------------------------------------------------- @@ -417,9 +425,24 @@ protected: // flush a directory's file entries and all its subdirectories to the internal data void FlushFileEntries( const u32 dirCluster, const u32 remainingFiles, const wxString& dirPath = L"", MemoryCardFileMetadataReference* parent = nullptr ); + // "delete" (prepend '_pcsx2_deleted_' to) any files that exist in oldFileEntries but no longer exist in m_fileEntryDict + void FlushDeletedFiles( const std::vector& oldFileEntries ); + + // recursive worker method of the above + // - newCluster: Current directory dotdir cluster of the new entries. + // - newFileCount: Number of file entries in the new directory. + // - dirPath: Path to the current directory relative to the root of the memcard. Must be identical for both entries. + void FlushDeletedFiles( const std::vector& oldFileEntries, const u32 newCluster, const u32 newFileCount, const wxString& dirPath ); + // write data as Save() normally would, but ignore the cache; used for flushing s32 WriteWithoutCache( const u8 *src, u32 adr, int size ); + // copies the contents of m_fileEntryDict into the tree structure fileEntryTree + void CopyEntryDictIntoTree( std::vector* fileEntryTree, const u32 cluster, const u32 fileCount ); + + // find equivalent (same name and type) of searchEntry in m_fileEntryDict in the directory indicated by cluster + const MemoryCardFileEntry* FindEquivalent( const MemoryCardFileEntry* searchEntry, const u32 cluster, const u32 fileCount ); + void SetTimeLastReadToNow(); void SetTimeLastWrittenToNow();