From 1d46800888d5487336bc6163770e3a55052eb0ad Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 10 Nov 2014 15:39:40 +0100 Subject: [PATCH 01/44] MemoryCard: Full initial implementation of the FolderMemoryCard. FileMemoryCard: Log reads and writes so I know what kind of commands I have to deal with. FolderMemoryCard: Create basic class/method outline based on FileMemoryCard. FolderMemoryCard: Add a FolderMemoryCardAggregator so I don't have to write every method in a way that has to handle more than one memory ca Also shuffle around the location of code because C++ cares about stuff needing to be defined before they're usable. FolderMemoryCard: Implement Open(). FolderMemoryCard: Implement GetSizeInfo(). FolderMemoryCard: Implement some basic structure of Read() FolderMemoryCard: Implement parts of Read() and Save(). Shouldn't it be Write() or Load()? Anyway, this doesn't work yet, but it gets part of the formatting procedure done which is at least something! FolderMemoryCard: Add method to calculate ECC. FolderMemoryCard: Start implementing the FAT. MemoryCard: More logging. FolderMemoryCard: Formatting works now! Formatted memory card isn't actually recognized as formatted yet because I don't store folder metadata yet, but we're getting there! FolderMemoryCard: Recognize when it's trying to access a data cluster. FolderMemoryCard: Add directory/file entry support. On further inspection this might not a be a good way to handle erasing. FolderMemoryCard: Method to get a file entry and file path from a file's data cluster. FolderMemoryCard: wxDirName is garbage, let's just use wxFileName for the folder too... FolderMemoryCard: Fix Erase method. FolderMemoryCard: Start implementing file writes. This is still quite broken but we're getting somewhere here! FolderMemoryCard: Load the data from the host file system into the memory card on emulation start. Also store superblock to host file system on end. FolderMemoryCard: Fix a few warnings. FolderMemoryCard: Implement file reads. FolderMemoryCard: Proper ECC reads. FolderMemoryCard: Reads to unmapped locations should return all 0xFF. FolderMemoryCard: Some sort of working WriteToFile. (Note: Doesn't always work depending on what order data gets written...) FolderMemoryCard: Forgot a 'b' for reading files in binary mode. Whoops. FolderMemoryCard: Load timestamps from the host filesystem. FolderMemoryCard: r+b needs the file to exist so create if it doesn't. FolderMemoryCard: Failsafe to allow non-sequential writes. FolderMemoryCard: Use a cache for writes. Does not flush to host FS yet! FolderMemoryCard: Flush the data written to the cache to the host file system on exit. FolderMemoryCard: Since we have a cache now, remove code related to formatting, it's no longer needed. FolderMemoryCard: More binary file mode mistakes... FolderMemoryCard: Make it actually possible to disable/eject cards. FileMemoryCard: Revert changes made for logging data. FolderMemoryCard: Remove excessive logging. MemoryCard: Note that the superblock struct is no longer unused. FolderMemoryCard: A disabled card shouldn't try writing data on exit. FolderMemoryCard: Log when flushing data. FolderMemoryCard: Replace plain constants with const variables. Should make it easier in the future to change the memory card size, if needed. FolderMemoryCard: Sort of handle the case when the total size of files in the memory card folder exceed the size of the card. Not elegant but prevents ugly errors. The file that caused the card to "overflow" will be seen as corrupted data by the PS2 browser. FolderMemoryCard: Some sanity checks. FolderMemoryCard: superBlock member really should have that m_ too to be consistent. MemoryCard: Switch back to FileMemoryCard for merging. FolderMemoryCard: Implement GetCRC() via a timestamp of the last memory card write. Reasoning: Regarding auto-ejecting on save load, I see that the current implementation checks that by comparing memory card CRC and reinserting if it mismatches. Since it's actually just about seeing if the memory card state of the savestate and the current state match, my GetCRC() now returns a millisecond timestamp of the last time the card was written to. This should work out to the intended result, though I had to use wxGetLocalTimeMillis() instead of wxGetUTCTimeMillis() since the latter isn't available for some reason. Fix GCC warnings and error. MemoryCard: Switch implementations via a #define. FolderMemoryCard: Add a NextFrame() method that should be called once per frame. Flushes written data to the host file system after a certain amout of frames have passed without any writes (currently 60). MemoryCard: Add the NextFrame() method to the plugin API. Counters: If the FolderMemoryCard is selected, inform it every frame in VSyncEnd() that a frame has passed. VSyncEnd: Probably better to inform the memory card before the frame limiting. Fix error when using wxWidgets >= 3.0. FolderMemoryCard: Extract into its own .h/.cpp files. FolderMemoryCard: Change cache to a map to reduce memory usage. FolderMemoryCard: More gracefully handle lack of space when adding files. --- common/include/PluginCallbacks.h | 7 +- pcsx2/CMakeLists.txt | 2 + pcsx2/Counters.cpp | 8 + pcsx2/PluginManager.cpp | 3 + pcsx2/Plugins.h | 1 + pcsx2/Sio.cpp | 8 + pcsx2/Sio.h | 7 + pcsx2/gui/MemoryCardFile.cpp | 45 +- pcsx2/gui/MemoryCardFile.h | 3 + pcsx2/gui/MemoryCardFolder.cpp | 961 ++++++++++++++++++ pcsx2/gui/MemoryCardFolder.h | 313 ++++++ pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj | 1 + .../VCprojects/pcsx2_vs2012.vcxproj.filters | 3 + pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj | 1 + .../VCprojects/pcsx2_vs2013.vcxproj.filters | 3 + 15 files changed, 1337 insertions(+), 29 deletions(-) create mode 100644 pcsx2/gui/MemoryCardFolder.cpp create mode 100644 pcsx2/gui/MemoryCardFolder.h diff --git a/common/include/PluginCallbacks.h b/common/include/PluginCallbacks.h index 744a71855..ea2ff1859 100644 --- a/common/include/PluginCallbacks.h +++ b/common/include/PluginCallbacks.h @@ -1104,7 +1104,12 @@ typedef struct _PS2E_ComponentAPI_Mcd u64 (PS2E_CALLBACK* McdGetCRC)( PS2E_THISPTR thisptr, uint port, uint slot ); - void* reserved[8]; + // McdNextFrame + // Inform the memory card that a frame of emulation time has passed. + // Used by the FolderMemoryCard to find a good time to flush written data to the host file system. + void (PS2E_CALLBACK* McdNextFrame)( PS2E_THISPTR thisptr, uint port, uint slot ); + + void* reserved[7]; } PS2E_ComponentAPI_Mcd; diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 351fd016e..9a958ce20 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -296,6 +296,7 @@ set(pcsx2GuiSources gui/MainFrame.cpp gui/MainMenuClicks.cpp gui/MemoryCardFile.cpp + gui/MemoryCardFolder.cpp gui/Panels/BaseApplicableConfigPanel.cpp gui/Panels/MemoryCardListPanel.cpp gui/MessageBoxes.cpp @@ -348,6 +349,7 @@ set(pcsx2GuiHeaders gui/IsoDropTarget.h gui/MainFrame.h gui/MemoryCardFile.h + gui/MemoryCardFolder.h gui/MSWstuff.h gui/Panels/ConfigurationPanels.h gui/Panels/LogOptionsPanels.h diff --git a/pcsx2/Counters.cpp b/pcsx2/Counters.cpp index 1eb8557ca..cc9c1c6a3 100644 --- a/pcsx2/Counters.cpp +++ b/pcsx2/Counters.cpp @@ -29,6 +29,8 @@ #include "ps2/HwInternal.h" +#include "Sio.h" + using namespace Threading; extern u8 psxhblankgate; @@ -428,6 +430,12 @@ static __fi void VSyncEnd(u32 sCycle) hwIntcIrq(INTC_VBLANK_E); // HW Irq psxVBlankEnd(); // psxCounters vBlank End if (gates) rcntEndGate(true, sCycle); // Counters End Gate Code + +#ifdef MEMORYCARD_USE_FOLDER + // FolderMemoryCard needs information on how much time has passed since the last write + sioNextFrame(); +#endif + frameLimit(); // limit FPS //Do this here, breaks Dynasty Warriors otherwise. diff --git a/pcsx2/PluginManager.cpp b/pcsx2/PluginManager.cpp index 28b8a2303..46802ff86 100644 --- a/pcsx2/PluginManager.cpp +++ b/pcsx2/PluginManager.cpp @@ -66,6 +66,9 @@ u64 SysPluginBindings::McdGetCRC( uint port, uint slot ) return Mcd->McdGetCRC( (PS2E_THISPTR) Mcd, port, slot ); } +void SysPluginBindings::McdNextFrame( uint port, uint slot ) { + Mcd->McdNextFrame( (PS2E_THISPTR) Mcd, port, slot ); +} // ---------------------------------------------------------------------------- // Yay, order of this array shouldn't be important. :) diff --git a/pcsx2/Plugins.h b/pcsx2/Plugins.h index 87f9505f3..5ed768c8a 100644 --- a/pcsx2/Plugins.h +++ b/pcsx2/Plugins.h @@ -241,6 +241,7 @@ public: void McdSave( uint port, uint slot, const u8 *src, u32 adr, int size ); void McdEraseBlock( uint port, uint slot, u32 adr ); u64 McdGetCRC( uint port, uint slot ); + void McdNextFrame( uint port, uint slot ); friend class SysCorePlugins; }; diff --git a/pcsx2/Sio.cpp b/pcsx2/Sio.cpp index f00137cce..aa6eac688 100644 --- a/pcsx2/Sio.cpp +++ b/pcsx2/Sio.cpp @@ -871,6 +871,14 @@ void SIODMAWrite(u8 value) sioWrite8inl(value); } +void sioNextFrame() { + for ( uint port = 0; port < 2; ++port ) { + for ( uint slot = 0; slot < 4; ++slot ) { + mcds[port][slot].NextFrame(); + } + } +} + void SaveStateBase::sioFreeze() { // CRCs for memory cards. diff --git a/pcsx2/Sio.h b/pcsx2/Sio.h index dc482336b..c57f7c049 100644 --- a/pcsx2/Sio.h +++ b/pcsx2/Sio.h @@ -19,6 +19,8 @@ // Games are highly unlikely to need timed IRQ's for PAD and MemoryCard handling anyway (rama). #define SIO_INLINE_IRQS +#include "MemoryCardFile.h" + struct _mcd { u8 term; // terminator value; @@ -80,6 +82,10 @@ struct _mcd { return SysPlugins.McdGetCRC(port, slot); } + + void NextFrame() { + SysPlugins.McdNextFrame( port, slot ); + } }; struct _sio @@ -117,3 +123,4 @@ extern void sioWriteCtrl16(u16 value); extern void sioInterrupt(); extern void InitializeSIO(u8 value); extern void SetForceMcdEjectTimeoutNow(); +extern void sioNextFrame(); diff --git a/pcsx2/gui/MemoryCardFile.cpp b/pcsx2/gui/MemoryCardFile.cpp index 7c3cd2bd2..e9dfd7486 100644 --- a/pcsx2/gui/MemoryCardFile.cpp +++ b/pcsx2/gui/MemoryCardFile.cpp @@ -16,8 +16,8 @@ #include "PrecompiledHeader.h" #include "Utilities/SafeArray.inl" #include - -#include "MemoryCardFile.h" +#include +#include // IMPORTANT! If this gets a macro redefinition error it means PluginCallbacks.h is included // in a global-scope header, and that's a BAD THING. Include it only into modules that need @@ -26,12 +26,16 @@ struct Component_FileMcd; #define PS2E_THISPTR Component_FileMcd* +#include "MemoryCardFile.h" +#include "MemoryCardFolder.h" + #include "System.h" #include "AppConfig.h" #include "svnrev.h" #include +#include static const int MCD_SIZE = 1024 * 8 * 16; // Legacy PSX card default size @@ -404,7 +408,11 @@ u64 FileMemoryCard::GetCRC( uint slot ) struct Component_FileMcd { PS2E_ComponentAPI_Mcd api; // callbacks the plugin provides back to the emulator +#ifdef MEMORYCARD_USE_FOLDER + FolderMemoryCardAggregator impl; +#else FileMemoryCard impl; // class-based implementations we refer to when API is invoked +#endif Component_FileMcd(); }; @@ -461,6 +469,12 @@ static u64 PS2E_CALLBACK FileMcd_GetCRC( PS2E_THISPTR thisptr, uint port, uint s return thisptr->impl.GetCRC( FileMcd_ConvertToSlot( port, slot ) ); } +static void PS2E_CALLBACK FileMcd_NextFrame( PS2E_THISPTR thisptr, uint port, uint slot ) { +#ifdef MEMORYCARD_USE_FOLDER + thisptr->impl.NextFrame( FileMcd_ConvertToSlot( port, slot ) ); +#endif +} + Component_FileMcd::Component_FileMcd() { memzero( api ); @@ -475,6 +489,7 @@ Component_FileMcd::Component_FileMcd() api.McdSave = FileMcd_Save; api.McdEraseBlock = FileMcd_EraseBlock; api.McdGetCRC = FileMcd_GetCRC; + api.McdNextFrame = FileMcd_NextFrame; } @@ -547,32 +562,6 @@ extern "C" const PS2E_LibraryAPI* FileMcd_InitAPI( const PS2E_EmulatorInfo* emui return &FileMcd_Library; } -// -------------------------------------------------------------------------------------- -// Currently Unused Superblock Header Struct -// -------------------------------------------------------------------------------------- -// (provided for reference purposes) - -struct superblock -{ - char magic[28]; // 0x00 - char version[12]; // 0x1c - u16 page_len; // 0x28 - u16 pages_per_cluster; // 0x2a - u16 pages_per_block; // 0x2c - u16 unused; // 0x2e - u32 clusters_per_card; // 0x30 - u32 alloc_offset; // 0x34 - u32 alloc_end; // 0x38 - u32 rootdir_cluster; // 0x3c - u32 backup_block1; // 0x40 - u32 backup_block2; // 0x44 - u32 ifc_list[32]; // 0x50 - u32 bad_block_list[32]; // 0xd0 - u8 card_type; // 0x150 - u8 card_flags; // 0x151 -}; - - //Tests if a string is a valid name for a new file within a specified directory. //returns true if: // - the file name has a minimum length of minNumCharacters chars (default is 5 chars: at least 1 char + '.' + 3-chars extension) diff --git a/pcsx2/gui/MemoryCardFile.h b/pcsx2/gui/MemoryCardFile.h index 9f27c3f93..387d6887b 100644 --- a/pcsx2/gui/MemoryCardFile.h +++ b/pcsx2/gui/MemoryCardFile.h @@ -15,6 +15,9 @@ #pragma once +// define this to use the FolderMemoryCard implementation instead of the regular FileMemoryCard one +//#define MEMORYCARD_USE_FOLDER + // NOTICE! This file is intended as a temporary placebo only, until such time that the // memorycard system is properly extracted into a plugin system (which would make it a // separate project file). diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp new file mode 100644 index 000000000..5ac35e92d --- /dev/null +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -0,0 +1,961 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2010 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" +#include "Utilities/SafeArray.inl" + +#include "MemoryCardFile.h" +#include "MemoryCardFolder.h" + +#include "System.h" +#include "AppConfig.h" + +#include "svnrev.h" + +FolderMemoryCard::FolderMemoryCard() { + m_slot = 0; + m_isEnabled = false; +} + +void FolderMemoryCard::InitializeInternalData() { + memset( &m_superBlock, 0xFF, sizeof( m_superBlock ) ); + memset( &m_indirectFat, 0xFF, sizeof( m_indirectFat ) ); + memset( &m_fat, 0xFF, sizeof( m_fat ) ); + memset( &m_backupBlock1, 0xFF, sizeof( m_backupBlock1 ) ); + memset( &m_backupBlock2, 0xFF, sizeof( m_backupBlock2 ) ); + m_cache.clear(); + m_timeLastWritten = 0; + m_isEnabled = false; + m_framesUntilFlush = 0; +} + +bool FolderMemoryCard::IsFormatted() { + // this should be a good enough arbitrary check, if someone can think of a case where this doesn't work feel free to change + return m_superBlock.raw[0x16] == 0x6F; +} + +void FolderMemoryCard::Open() { + InitializeInternalData(); + + wxFileName configuredFileName( g_Conf->FullpathToMcd( m_slot ) ); + folderName = wxFileName( configuredFileName.GetFullPath() + L"/" ); + wxString str( configuredFileName.GetFullPath() ); + bool disabled = false; + + if ( g_Conf->Mcd[m_slot].Enabled && g_Conf->Mcd[m_slot].Type == MemoryCardType::MemoryCard_Folder ) { + if ( configuredFileName.GetFullName().IsEmpty() ) { + str = L"[empty filename]"; + disabled = true; + } + if ( !disabled && configuredFileName.FileExists() ) { + str = L"[is file, should be folder]"; + disabled = true; + } + + // if nothing exists at a valid location, create a directory for the memory card + if ( !disabled && !folderName.DirExists() ) { + if ( !folderName.Mkdir() ) { + str = L"[couldn't create folder]"; + disabled = true; + } + } + } else { + str = L"[disabled]"; + disabled = true; + } + + Console.WriteLn( disabled ? Color_Gray : Color_Green, L"McdSlot %u: [Folder] " + str, m_slot ); + if ( disabled ) return; + + m_isEnabled = true; + LoadMemoryCardData(); + + SetTimeLastWrittenToNow(); + m_framesUntilFlush = 0; +} + +void FolderMemoryCard::Close() { + if ( !m_isEnabled ) { return; } + + Flush(); + + wxFileName superBlockFileName( folderName.GetPath(), L"_pcsx2_superblock" ); + wxFFile superBlockFile( superBlockFileName.GetFullPath().c_str(), L"wb" ); + if ( superBlockFile.IsOpened() ) { + superBlockFile.Write( &m_superBlock.raw, sizeof( m_superBlock.raw ) ); + } +} + +void FolderMemoryCard::LoadMemoryCardData() { + bool formatted = false; + + // read superblock if it exists + wxFileName superBlockFileName( folderName.GetPath(), L"_pcsx2_superblock" ); + if ( superBlockFileName.FileExists() ) { + wxFFile superBlockFile( superBlockFileName.GetFullPath().c_str(), L"rb" ); + if ( superBlockFile.IsOpened() && superBlockFile.Read( &m_superBlock.raw, sizeof( m_superBlock.raw ) ) >= sizeof( m_superBlock.data ) ) { + formatted = IsFormatted(); + } + } + + // if superblock was valid, load folders and files + if ( formatted ) { + CreateFat(); + CreateRootDir(); + MemoryCardFileEntry* const rootDirEntry = &m_fileEntryDict[m_superBlock.data.rootdir_cluster].entries[0]; + AddFolder( rootDirEntry, folderName.GetPath() ); + } +} + +void FolderMemoryCard::CreateFat() { + const u32 totalClusters = m_superBlock.data.clusters_per_card; + const u32 clusterSize = m_superBlock.data.page_len * m_superBlock.data.pages_per_cluster; + const u32 fatEntriesPerCluster = clusterSize / 4; + const u32 countFatClusters = ( totalClusters % fatEntriesPerCluster ) != 0 ? ( totalClusters / fatEntriesPerCluster + 1 ) : ( totalClusters / fatEntriesPerCluster ); + const u32 countDataClusters = m_superBlock.data.alloc_end; + + // create indirect FAT + for ( unsigned int i = 0; i < countFatClusters; ++i ) { + m_indirectFat.data[0][i] = GetFreeSystemCluster(); + } + + // fill FAT with default values + for ( unsigned int i = 0; i < countDataClusters; ++i ) { + m_fat.data[0][0][i] = 0x7FFFFFFFu; + } +} + +void FolderMemoryCard::CreateRootDir() { + MemoryCardFileEntryCluster* const rootCluster = &m_fileEntryDict[m_superBlock.data.rootdir_cluster]; + memset( &rootCluster->entries[0].entry.raw[0], 0x00, 0x200 ); + rootCluster->entries[0].entry.data.mode = 0x8427; + rootCluster->entries[0].entry.data.length = 2; + rootCluster->entries[0].entry.data.name[0] = '.'; + + memset( &rootCluster->entries[1].entry.raw[0], 0x00, 0x200 ); + rootCluster->entries[1].entry.data.mode = 0xA426; + rootCluster->entries[1].entry.data.name[0] = '.'; + rootCluster->entries[1].entry.data.name[1] = '.'; + + // mark root dir cluster as used + m_fat.data[0][0][m_superBlock.data.rootdir_cluster] = 0xFFFFFFFFu; +} + +u32 FolderMemoryCard::GetFreeSystemCluster() { + // first block is reserved for superblock + u32 highestUsedCluster = ( m_superBlock.data.pages_per_block / m_superBlock.data.pages_per_cluster ) - 1; + + // can't use any of the indirect fat clusters + for ( int i = 0; i < IndirectFatClusterCount; ++i ) { + highestUsedCluster = std::max( highestUsedCluster, m_superBlock.data.ifc_list[i] ); + } + + // or fat clusters + for ( int i = 0; i < IndirectFatClusterCount; ++i ) { + for ( int j = 0; j < ClusterSize / 4; ++j ) { + if ( m_indirectFat.data[i][j] != 0xFFFFFFFFu ) { + highestUsedCluster = std::max( highestUsedCluster, m_indirectFat.data[i][j] ); + } + } + } + + return highestUsedCluster + 1; +} + +u32 FolderMemoryCard::GetFreeDataCluster() { + // BIOS reports different cluster values than what the memory card actually has, match that when adding files + // 8mb card -> BIOS: 7999 clusters / Superblock: 8135 clusters + // 16mb card -> BIOS: 15999 clusters / Superblock: 16295 clusters + // 32mb card -> BIOS: 31999 clusters / Superblock: 32615 clusters + // 64mb card -> BIOS: 64999 clusters / Superblock: 65255 clusters + const u32 countDataClusters = ( m_superBlock.data.alloc_end / 1000 ) * 1000 - 1; + + for ( unsigned int i = 0; i < countDataClusters; ++i ) { + const u32 cluster = m_fat.data[0][0][i]; + + if ( ( cluster & 0x80000000 ) == 0 ) { + return i; + } + } + + return 0xFFFFFFFF; +} + +u32 FolderMemoryCard::GetAmountFreeDataClusters() { + const u32 countDataClusters = ( m_superBlock.data.alloc_end / 1000 ) * 1000 - 1; + u32 countFreeDataClusters = 0; + + for ( unsigned int i = 0; i < countDataClusters; ++i ) { + const u32 cluster = m_fat.data[0][0][i]; + + if ( ( cluster & 0x80000000 ) == 0 ) { + ++countFreeDataClusters; + } + } + + return countFreeDataClusters; +} + +u32 FolderMemoryCard::GetLastClusterOfData( const u32 cluster ) { + u32 entryCluster; + u32 nextCluster = cluster; + do { + entryCluster = nextCluster; + nextCluster = m_fat.data[0][0][entryCluster] & 0x7FFFFFFF; + } while ( nextCluster != 0x7FFFFFFF ); + return entryCluster; +} + +u64 FolderMemoryCard::ConvertToMemoryCardTimestamp( const wxDateTime& time ) { + if ( !time.IsValid() ) { + return 0; + } + + union { + MemoryCardFileEntryDateTime data; + u64 value; + } t; + + wxDateTime::Tm tm = time.GetTm( wxDateTime::GMT9 ); + + t.data.unused = 0; + t.data.second = tm.sec; + t.data.minute = tm.min; + t.data.hour = tm.hour; + t.data.day = tm.mday; + t.data.month = tm.mon + 1; + t.data.year = tm.year; + + return t.value; +} + +MemoryCardFileEntry* FolderMemoryCard::AppendFileEntryToDir( MemoryCardFileEntry* const dirEntry ) { + u32 entryCluster = GetLastClusterOfData( dirEntry->entry.data.cluster ); + + MemoryCardFileEntry* newFileEntry; + if ( dirEntry->entry.data.length % 2 == 0 ) { + // need new cluster + u32 newCluster = GetFreeDataCluster(); + if ( newCluster == 0xFFFFFFFFu ) { return nullptr; } + m_fat.data[0][0][entryCluster] = newCluster | 0x80000000; + m_fat.data[0][0][newCluster] = 0xFFFFFFFF; + newFileEntry = &m_fileEntryDict[newCluster].entries[0]; + } else { + // can use last page of existing clusters + newFileEntry = &m_fileEntryDict[entryCluster].entries[1]; + } + + return newFileEntry; +} + +bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath ) { + wxDir dir( dirPath ); + if ( dir.IsOpened() ) { + Console.WriteLn( L"(FolderMcd) Adding folder: %s", WX_STR( dirPath ) ); + + const u32 dirStartCluster = dirEntry->entry.data.cluster; + + wxString fileName; + bool hasNext; + + int entryNumber = 2; // include . and .. + hasNext = dir.GetFirst( &fileName ); + while ( hasNext ) { + wxFileName fileInfo( dirPath, fileName ); + bool isFile = wxFile::Exists( fileInfo.GetFullPath() ); + + if ( isFile ) { + if ( !fileName.StartsWith( L"_pcsx2_" ) ) { + if ( AddFile( dirEntry, dirPath, fileName ) ) { + ++entryNumber; + } + } + } else { + // make sure we have enough space on the memcard for the directory + const u32 newNeededClusters = ( dirEntry->entry.data.length % 2 ) == 0 ? 2 : 1; + if ( newNeededClusters > GetAmountFreeDataClusters() ) { + Console.Warning( GetCardFullMessage( fileName ) ); + hasNext = dir.GetNext( &fileName ); + continue; + } + + // is a subdirectory + wxDateTime creationTime, modificationTime; + fileInfo.AppendDir( fileInfo.GetFullName() ); + fileInfo.SetName( L"" ); + fileInfo.ClearExt(); + fileInfo.GetTimes( NULL, &modificationTime, &creationTime ); + + // add entry for subdir in parent dir + MemoryCardFileEntry* newDirEntry = AppendFileEntryToDir( dirEntry ); + dirEntry->entry.data.length++; + newDirEntry->entry.data.mode = 0x8427; + newDirEntry->entry.data.length = 2; + newDirEntry->entry.data.timeCreated.value = ConvertToMemoryCardTimestamp( creationTime ); + newDirEntry->entry.data.timeModified.value = ConvertToMemoryCardTimestamp( modificationTime ); + strcpy( (char*)&newDirEntry->entry.data.name[0], fileName.mbc_str() ); + + // create new cluster for . and .. entries + u32 newCluster = GetFreeDataCluster(); + m_fat.data[0][0][newCluster] = 0xFFFFFFFF; + newDirEntry->entry.data.cluster = newCluster; + + MemoryCardFileEntryCluster* const subDirCluster = &m_fileEntryDict[newCluster]; + memset( &subDirCluster->entries[0].entry.raw[0], 0x00, 0x200 ); + subDirCluster->entries[0].entry.data.mode = 0x8427; + subDirCluster->entries[0].entry.data.dirEntry = entryNumber; + subDirCluster->entries[0].entry.data.name[0] = '.'; + + memset( &subDirCluster->entries[1].entry.raw[0], 0x00, 0x200 ); + subDirCluster->entries[1].entry.data.mode = 0x8427; + subDirCluster->entries[1].entry.data.name[0] = '.'; + subDirCluster->entries[1].entry.data.name[1] = '.'; + + ++entryNumber; + + // and add all files in subdir + AddFolder( newDirEntry, fileInfo.GetFullPath() ); + } + + hasNext = dir.GetNext( &fileName ); + } + + return true; + } + + return false; +} + +bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName ) { + wxFileName relativeFilePath( dirPath, fileName ); + relativeFilePath.MakeRelativeTo( folderName.GetPath() ); + Console.WriteLn( L"(FolderMcd) Adding file: %s", WX_STR( relativeFilePath.GetFullPath() ) ); + + wxFileName fileInfo( dirPath, fileName ); + wxFFile file( fileInfo.GetFullPath(), L"rb" ); + if ( file.IsOpened() ) { + // make sure we have enough space on the memcard to hold the data + const u32 clusterSize = m_superBlock.data.pages_per_cluster * m_superBlock.data.page_len; + const u32 filesize = file.Length(); + const u32 countClusters = ( filesize % clusterSize ) != 0 ? ( filesize / clusterSize + 1 ) : ( filesize / clusterSize ); + const u32 newNeededClusters = ( dirEntry->entry.data.length % 2 ) == 0 ? countClusters + 1 : countClusters; + if ( newNeededClusters > GetAmountFreeDataClusters() ) { + Console.Warning( GetCardFullMessage( relativeFilePath.GetFullPath() ) ); + file.Close(); + return false; + } + + MemoryCardFileEntry* newFileEntry = AppendFileEntryToDir( dirEntry ); + wxDateTime creationTime, modificationTime; + fileInfo.GetTimes( NULL, &modificationTime, &creationTime ); + + // set file entry data + memset( &newFileEntry->entry.raw[0], 0x00, 0x200 ); + newFileEntry->entry.data.mode = 0x8497; + newFileEntry->entry.data.length = filesize; + newFileEntry->entry.data.timeCreated.value = ConvertToMemoryCardTimestamp( creationTime ); + newFileEntry->entry.data.timeModified.value = ConvertToMemoryCardTimestamp( modificationTime ); + u32 fileDataStartingCluster = GetFreeDataCluster(); + newFileEntry->entry.data.cluster = fileDataStartingCluster; + strcpy( (char*)&newFileEntry->entry.data.name[0], fileName.mbc_str() ); + + // mark the appropriate amount of clusters as used + u32 dataCluster = fileDataStartingCluster; + m_fat.data[0][0][dataCluster] = 0xFFFFFFFF; + for ( unsigned int i = 0; i < countClusters - 1; ++i ) { + u32 newCluster = GetFreeDataCluster(); + m_fat.data[0][0][dataCluster] = newCluster | 0x80000000; + m_fat.data[0][0][newCluster] = 0xFFFFFFFF; + dataCluster = newCluster; + } + + file.Close(); + } else { + Console.WriteLn( L"(FolderMcd) Could not open file: %s", WX_STR( relativeFilePath.GetFullPath() ) ); + return false; + } + + // and finally, increase file count in the directory entry + dirEntry->entry.data.length++; + + return true; +} + +s32 FolderMemoryCard::IsPresent() { + return m_isEnabled; +} + +void FolderMemoryCard::GetSizeInfo( PS2E_McdSizeInfo& outways ) { + outways.SectorSize = PageSize; + outways.EraseBlockSizeInSectors = BlockSize / PageSize; + outways.McdSizeInSectors = TotalPages; + + u8 *pdata = (u8*)&outways.McdSizeInSectors; + outways.Xor = 18; + outways.Xor ^= pdata[0] ^ pdata[1] ^ pdata[2] ^ pdata[3]; +} + +bool FolderMemoryCard::IsPSX() { + return false; +} + +u8* FolderMemoryCard::GetSystemBlockPointer( const u32 adr ) { + const u32 block = adr / BlockSizeRaw; + const u32 page = adr / PageSizeRaw; + const u32 offset = adr % PageSizeRaw; + const u32 cluster = adr / ClusterSizeRaw; + + const u32 startDataCluster = m_superBlock.data.alloc_offset; + const u32 endDataCluster = startDataCluster + m_superBlock.data.alloc_end; + if ( cluster >= startDataCluster && cluster < endDataCluster ) { + // trying to access a file entry? + const u32 fatCluster = cluster - m_superBlock.data.alloc_offset; + return GetFileEntryPointer( m_superBlock.data.rootdir_cluster, fatCluster, page % 2, offset ); + } + + u8* src = nullptr; + if ( block == 0 ) { + src = &m_superBlock.raw[page * PageSize + offset]; + } else if ( block == m_superBlock.data.backup_block1 ) { + src = &m_backupBlock1[( page % 16 ) * PageSize + offset]; + } else if ( block == m_superBlock.data.backup_block2 ) { + src = &m_backupBlock2[( page % 16 ) * PageSize + offset]; + } else { + // trying to access indirect FAT? + for ( int i = 0; i < IndirectFatClusterCount; ++i ) { + if ( cluster == m_superBlock.data.ifc_list[i] ) { + src = &m_indirectFat.raw[i][( page % 2 ) * PageSize + offset]; + return src; + } + } + // trying to access FAT? + for ( int i = 0; i < IndirectFatClusterCount; ++i ) { + for ( int j = 0; j < ClusterSize / 4; ++j ) { + const u32 fatCluster = m_indirectFat.data[i][j]; + if ( fatCluster != 0xFFFFFFFFu && fatCluster == cluster ) { + src = &m_fat.raw[i][j][( page % 2 ) * PageSize + offset]; + return src; + } + } + } + } + + return src; +} + +u8* FolderMemoryCard::GetFileEntryPointer( const u32 currentCluster, const u32 searchCluster, const u32 entryNumber, const u32 offset ) { + // we found the correct cluster, return pointer to it + if ( currentCluster == searchCluster ) { + return &m_fileEntryDict[currentCluster].entries[entryNumber].entry.raw[offset]; + } + + // check other clusters of this directory + const u32 nextCluster = m_fat.data[0][0][currentCluster] & 0x7FFFFFFF; + if ( nextCluster != 0x7FFFFFFF ) { + u8* ptr = GetFileEntryPointer( nextCluster, searchCluster, entryNumber, offset ); + if ( ptr != nullptr ) { return ptr; } + } + + // check subdirectories + for ( int i = 0; i < 2; ++i ) { + MemoryCardFileEntry* const entry = &m_fileEntryDict[currentCluster].entries[i]; + if ( entry->IsUsed() && entry->IsDir() && entry->entry.data.cluster != 0 ) { + u8* ptr = GetFileEntryPointer( entry->entry.data.cluster, searchCluster, entryNumber, offset ); + if ( ptr != nullptr ) { return ptr; } + } + } + + return nullptr; +} + +MemoryCardFileEntry* FolderMemoryCard::GetFileEntryFromFileDataCluster( const u32 currentCluster, const u32 searchCluster, wxFileName* fileName, const size_t originalDirCount, u32* outClusterNumber ) { + // check both entries of the current cluster if they're the file we're searching for, and if yes return it + for ( int i = 0; i < 2; ++i ) { + MemoryCardFileEntry* const entry = &m_fileEntryDict[currentCluster].entries[i]; + if ( entry->IsUsed() && entry->IsFile() ) { + u32 fileCluster = entry->entry.data.cluster; + u32 clusterNumber = 0; + do { + if ( fileCluster == searchCluster ) { + fileName->SetName( wxString::FromAscii( (const char*)entry->entry.data.name ) ); + *outClusterNumber = clusterNumber; + return entry; + } + ++clusterNumber; + } while ( ( fileCluster = m_fat.data[0][0][fileCluster] & 0x7FFFFFFF ) != 0x7FFFFFFF ); + // There's a lot of optimization work that can be done here, looping through all clusters of every single file + // is not very efficient, especially since files are going to be accessed from the start and in-order the vast + // majority of the time. You can probably cut a lot of the work by remembering the state of the last access + // and only checking if the current access is either the same or the next cluster according to the FAT. + //} while ( false ); + } + } + + // check other clusters of this directory + // this can probably be solved more efficiently by looping through nextClusters instead of recursively calling + const u32 nextCluster = m_fat.data[0][0][currentCluster] & 0x7FFFFFFF; + if ( nextCluster != 0x7FFFFFFF ) { + MemoryCardFileEntry* ptr = GetFileEntryFromFileDataCluster( nextCluster, searchCluster, fileName, originalDirCount, outClusterNumber ); + if ( ptr != nullptr ) { return ptr; } + } + + // check subdirectories + for ( int i = 0; i < 2; ++i ) { + MemoryCardFileEntry* const entry = &m_fileEntryDict[currentCluster].entries[i]; + if ( entry->IsUsed() && entry->IsDir() && entry->entry.data.cluster != 0 ) { + MemoryCardFileEntry* ptr = GetFileEntryFromFileDataCluster( entry->entry.data.cluster, searchCluster, fileName, originalDirCount, outClusterNumber ); + if ( ptr != nullptr ) { + fileName->InsertDir( originalDirCount, wxString::FromAscii( (const char*)entry->entry.data.name ) ); + return ptr; + } + } + } + + return nullptr; +} + +bool FolderMemoryCard::ReadFromFile( u8 *dest, u32 adr, u32 dataLength ) { + const u32 page = adr / PageSizeRaw; + const u32 offset = adr % PageSizeRaw; + const u32 cluster = adr / ClusterSizeRaw; + const u32 fatCluster = cluster - m_superBlock.data.alloc_offset; + + // figure out which file to read from + wxFileName fileName( folderName ); + u32 clusterNumber; + const MemoryCardFileEntry* const entry = GetFileEntryFromFileDataCluster( m_superBlock.data.rootdir_cluster, fatCluster, &fileName, fileName.GetDirCount(), &clusterNumber ); + if ( entry != nullptr ) { + if ( !fileName.DirExists() ) { + fileName.Mkdir(); + } + wxFFile file( fileName.GetFullPath(), L"rb" ); + if ( file.IsOpened() ) { + const u32 clusterOffset = ( page % 2 ) * PageSize + offset; + const u32 fileOffset = clusterNumber * ClusterSize + clusterOffset; + + file.Seek( fileOffset ); + size_t bytesRead = file.Read( dest, dataLength ); + + // if more bytes were requested than actually exist, fill the rest with 0xFF + if ( bytesRead < dataLength ) { + memset( &dest[bytesRead], 0xFF, dataLength - bytesRead ); + } + + file.Close(); + + return bytesRead > 0; + } + } + + return false; +} + +s32 FolderMemoryCard::Read( u8 *dest, u32 adr, int size ) { + const u32 block = adr / BlockSizeRaw; + const u32 page = adr / PageSizeRaw; + const u32 offset = adr % PageSizeRaw; + const u32 cluster = adr / ClusterSizeRaw; + const u32 end = offset + size; + + if ( end > PageSizeRaw ) { + // is trying to read more than one page at a time + // do this recursively so that each function call only has to care about one page + const u32 toNextPage = PageSizeRaw - offset; + Read( dest + toNextPage, adr + toNextPage, size - toNextPage ); + size = toNextPage; + } + + if ( offset < PageSize ) { + // is trying to read (part of) an actual data block + const u32 dataOffset = 0; + const u32 dataLength = std::min( (u32)size, (u32)( PageSize - offset ) ); + + // if we have a cache for this page, just load from that + auto it = m_cache.find( page ); + if ( it != m_cache.end() ) { + memcpy( dest, &it->second.raw[offset], dataLength ); + } else { + u8* src = GetSystemBlockPointer( adr ); + if ( src != nullptr ) { + memcpy( dest, src, dataLength ); + } else { + if ( !ReadFromFile( dest, adr, dataLength ) ) { + memset( dest, 0xFF, dataLength ); + } + } + } + } + + if ( end > PageSize ) { + // is trying to (partially) read the ECC + const u32 eccOffset = PageSize - offset; + const u32 eccLength = std::min( (u32)( size - offset ), (u32)EccSize ); + const u32 adrStart = page * 0x210u; + + u8 data[PageSize]; + Read( data, adrStart, PageSize ); + + u8 ecc[EccSize]; + memset( ecc, 0xFF, EccSize ); + + for ( int i = 0; i < PageSize / 0x80; ++i ) { + FolderMemoryCard::CalculateECC( ecc + ( i * 3 ), &data[i * 0x80] ); + } + + memcpy( dest + eccOffset, ecc, eccLength ); + } + + // return 0 on fail, 1 on success? + return 1; +} + +s32 FolderMemoryCard::Save( const u8 *src, u32 adr, int size ) { + const u32 block = adr / BlockSizeRaw; + const u32 cluster = adr / ClusterSizeRaw; + const u32 page = adr / PageSizeRaw; + const u32 offset = adr % PageSizeRaw; + const u32 end = offset + size; + + if ( end > PageSizeRaw ) { + // is trying to store more than one page at a time + // do this recursively so that each function call only has to care about one page + const u32 toNextPage = PageSizeRaw - offset; + Save( src + toNextPage, adr + toNextPage, size - toNextPage ); + size = toNextPage; + } + + if ( offset < PageSize ) { + // is trying to store (part of) an actual data block + const u32 dataLength = std::min( (u32)size, PageSize - offset ); + + // if cache page has not yet been touched, fill it with the data from our memory card + auto it = m_cache.find( page ); + MemoryCardPage* cachePage; + if ( it == m_cache.end() ) { + cachePage = &m_cache[page]; + const u32 adrLoad = page * PageSizeRaw; + Read( &cachePage->raw[0], adrLoad, PageSize ); + } else { + cachePage = &it->second; + } + + // then just write to the cache + memcpy( &cachePage->raw[offset], src, dataLength ); + + SetTimeLastWrittenToNow(); + } + + return 1; +} + +void FolderMemoryCard::NextFrame() { + if ( m_framesUntilFlush > 0 && --m_framesUntilFlush == 0 ) { + Flush(); + } +} + +void FolderMemoryCard::Flush() { + Console.WriteLn( L"(FolderMcd) Writing data for slot %u to file system.", m_slot ); + + // first write the superblock if necessary + Flush( 0 ); + + if ( !IsFormatted() ) { return; } + + // then write the indirect FAT + for ( int i = 0; i < IndirectFatClusterCount; ++i ) { + const u32 cluster = m_superBlock.data.ifc_list[i]; + if ( cluster > 0 && cluster < TotalClusters ) { + const u32 page = cluster * 2; + Flush( page ); + Flush( page + 1 ); + } + } + + // and the FAT + for ( int i = 0; i < IndirectFatClusterCount; ++i ) { + for ( int j = 0; j < ClusterSize / 4; ++j ) { + const u32 cluster = m_indirectFat.data[i][j]; + if ( cluster > 0 && cluster < TotalClusters ) { + const u32 page = cluster * 2; + Flush( page ); + Flush( page + 1 ); + } + } + } + + // then all directory and file entries + const u32 rootDirCluster = m_superBlock.data.rootdir_cluster; + const u32 rootDirPage = ( rootDirCluster + m_superBlock.data.alloc_offset ) * 2; + Flush( rootDirPage ); + MemoryCardFileEntryCluster* rootEntries = &m_fileEntryDict[rootDirCluster]; + if ( rootEntries->entries[0].IsValid() && rootEntries->entries[0].IsUsed() ) { + FlushFileEntries( rootDirCluster, rootEntries->entries[0].entry.data.length ); + } + + // and finally, flush everything that hasn't been flushed yet + for ( int i = 0; i < TotalPages; ++i ) { + Flush( i ); + } + +} + +void FolderMemoryCard::Flush( const u32 page ) { + if ( page >= TotalPages ) { return; } + auto it = m_cache.find( page ); + if ( it != m_cache.end() ) { + WriteWithoutCache( &it->second.raw[0], page * PageSizeRaw, PageSize ); + m_cache.erase( it ); + } +} + +void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remainingFiles ) { + // flush the current cluster + const u32 page = ( dirCluster + m_superBlock.data.alloc_offset ) * 2; + Flush( page ); + Flush( page + 1 ); + + // if either of the current entries is a subdir, flush that too + MemoryCardFileEntryCluster* entries = &m_fileEntryDict[dirCluster]; + const u32 filesInThisCluster = std::min( remainingFiles, 2u ); + for ( unsigned int i = 0; i < filesInThisCluster; ++i ) { + MemoryCardFileEntry* entry = &entries->entries[i]; + if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() ) { + const u32 cluster = entry->entry.data.cluster; + if ( cluster > 0 ) { + FlushFileEntries( cluster, entry->entry.data.length ); + } + } + } + + // continue to the next cluster of this directory + const u32 nextCluster = m_fat.data[0][0][dirCluster]; + if ( nextCluster != 0xFFFFFFFF ) { + FlushFileEntries( nextCluster & 0x7FFFFFFF, remainingFiles - 2 ); + } +} + +s32 FolderMemoryCard::WriteWithoutCache( const u8 *src, u32 adr, int size ) { + const u32 block = adr / BlockSizeRaw; + const u32 cluster = adr / ClusterSizeRaw; + const u32 page = adr / PageSizeRaw; + const u32 offset = adr % PageSizeRaw; + const u32 end = offset + size; + + if ( end > PageSizeRaw ) { + // is trying to store more than one page at a time + // do this recursively so that each function call only has to care about one page + const u32 toNextPage = PageSizeRaw - offset; + Save( src + toNextPage, adr + toNextPage, size - toNextPage ); + size = toNextPage; + } + + if ( offset < PageSize ) { + // is trying to store (part of) an actual data block + const u32 dataLength = std::min( (u32)size, PageSize - offset ); + + u8* dest = GetSystemBlockPointer( adr ); + if ( dest != nullptr ) { + memcpy( dest, src, dataLength ); + } else { + WriteToFile( src, adr, dataLength ); + } + } + + if ( end > PageSize ) { + // is trying to store ECC + // simply ignore this, is automatically generated when reading + } + + // return 0 on fail, 1 on success? + return 1; +} + +bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { + const u32 cluster = adr / ClusterSizeRaw; + const u32 page = adr / PageSizeRaw; + const u32 offset = adr % PageSizeRaw; + const u32 fatCluster = cluster - m_superBlock.data.alloc_offset; + + // figure out which file to write to + wxFileName fileName( folderName ); + u32 clusterNumber; + const MemoryCardFileEntry* const entry = GetFileEntryFromFileDataCluster( m_superBlock.data.rootdir_cluster, fatCluster, &fileName, fileName.GetDirCount(), &clusterNumber ); + if ( entry != nullptr ) { + if ( !fileName.DirExists() ) { + fileName.Mkdir(); + } + if ( !fileName.FileExists() ) { + wxFFile createEmptyFile( fileName.GetFullPath(), L"wb" ); + createEmptyFile.Close(); + } + wxFFile file( fileName.GetFullPath(), L"r+b" ); + if ( file.IsOpened() ) { + const u32 clusterOffset = ( page % 2 ) * PageSize + offset; + const u32 fileSize = entry->entry.data.length; + const u32 fileOffsetStart = std::min( clusterNumber * ClusterSize + clusterOffset, fileSize );; + const u32 fileOffsetEnd = std::min( fileOffsetStart + dataLength, fileSize ); + const u32 bytesToWrite = fileOffsetEnd - fileOffsetStart; + + wxFileOffset actualFileSize = file.Length(); + if ( actualFileSize < fileOffsetStart ) { + const u32 diff = fileOffsetStart - actualFileSize; + u8 temp = 0xFF; + for ( u32 i = 0; i < diff; ++i ) { + file.Write( &temp, 1 ); + } + } + + file.Seek( fileOffsetStart ); + if ( bytesToWrite > 0 ) { + file.Write( src, bytesToWrite ); + } + + file.Close(); + + return true; + } + } + + return false; +} + +s32 FolderMemoryCard::EraseBlock( u32 adr ) { + const u32 block = adr / BlockSizeRaw; + + u8 eraseData[PageSize]; + memset( eraseData, 0xFF, PageSize ); + for ( int page = 0; page < 16; ++page ) { + const u32 adr = block * BlockSizeRaw + page * PageSizeRaw; + Save( eraseData, adr, PageSize ); + } + + // return 0 on fail, 1 on success? + return 1; +} + +u64 FolderMemoryCard::GetCRC() { + // Since this is just used as integrity check for savestate loading, + // give a timestamp of the last time the memory card was written to + return m_timeLastWritten; +} + +void FolderMemoryCard::SetSlot( uint slot ) { + pxAssert( slot < 8 ); + m_slot = slot; +} + +void FolderMemoryCard::SetTimeLastWrittenToNow() { + m_timeLastWritten = wxGetLocalTimeMillis().GetValue(); + m_framesUntilFlush = FramesAfterWriteUntilFlush; +} + +// from http://www.oocities.org/siliconvalley/station/8269/sma02/sma02.html#ECC +void FolderMemoryCard::CalculateECC( u8* ecc, const u8* data ) { + static const u8 Table[] = { + 0x00, 0x87, 0x96, 0x11, 0xa5, 0x22, 0x33, 0xb4, 0xb4, 0x33, 0x22, 0xa5, 0x11, 0x96, 0x87, 0x00, + 0xc3, 0x44, 0x55, 0xd2, 0x66, 0xe1, 0xf0, 0x77, 0x77, 0xf0, 0xe1, 0x66, 0xd2, 0x55, 0x44, 0xc3, + 0xd2, 0x55, 0x44, 0xc3, 0x77, 0xf0, 0xe1, 0x66, 0x66, 0xe1, 0xf0, 0x77, 0xc3, 0x44, 0x55, 0xd2, + 0x11, 0x96, 0x87, 0x00, 0xb4, 0x33, 0x22, 0xa5, 0xa5, 0x22, 0x33, 0xb4, 0x00, 0x87, 0x96, 0x11, + 0xe1, 0x66, 0x77, 0xf0, 0x44, 0xc3, 0xd2, 0x55, 0x55, 0xd2, 0xc3, 0x44, 0xf0, 0x77, 0x66, 0xe1, + 0x22, 0xa5, 0xb4, 0x33, 0x87, 0x00, 0x11, 0x96, 0x96, 0x11, 0x00, 0x87, 0x33, 0xb4, 0xa5, 0x22, + 0x33, 0xb4, 0xa5, 0x22, 0x96, 0x11, 0x00, 0x87, 0x87, 0x00, 0x11, 0x96, 0x22, 0xa5, 0xb4, 0x33, + 0xf0, 0x77, 0x66, 0xe1, 0x55, 0xd2, 0xc3, 0x44, 0x44, 0xc3, 0xd2, 0x55, 0xe1, 0x66, 0x77, 0xf0, + 0xf0, 0x77, 0x66, 0xe1, 0x55, 0xd2, 0xc3, 0x44, 0x44, 0xc3, 0xd2, 0x55, 0xe1, 0x66, 0x77, 0xf0, + 0x33, 0xb4, 0xa5, 0x22, 0x96, 0x11, 0x00, 0x87, 0x87, 0x00, 0x11, 0x96, 0x22, 0xa5, 0xb4, 0x33, + 0x22, 0xa5, 0xb4, 0x33, 0x87, 0x00, 0x11, 0x96, 0x96, 0x11, 0x00, 0x87, 0x33, 0xb4, 0xa5, 0x22, + 0xe1, 0x66, 0x77, 0xf0, 0x44, 0xc3, 0xd2, 0x55, 0x55, 0xd2, 0xc3, 0x44, 0xf0, 0x77, 0x66, 0xe1, + 0x11, 0x96, 0x87, 0x00, 0xb4, 0x33, 0x22, 0xa5, 0xa5, 0x22, 0x33, 0xb4, 0x00, 0x87, 0x96, 0x11, + 0xd2, 0x55, 0x44, 0xc3, 0x77, 0xf0, 0xe1, 0x66, 0x66, 0xe1, 0xf0, 0x77, 0xc3, 0x44, 0x55, 0xd2, + 0xc3, 0x44, 0x55, 0xd2, 0x66, 0xe1, 0xf0, 0x77, 0x77, 0xf0, 0xe1, 0x66, 0xd2, 0x55, 0x44, 0xc3, + 0x00, 0x87, 0x96, 0x11, 0xa5, 0x22, 0x33, 0xb4, 0xb4, 0x33, 0x22, 0xa5, 0x11, 0x96, 0x87, 0x00 + }; + + int i, c; + + ecc[0] = ecc[1] = ecc[2] = 0; + + for ( i = 0; i < 0x80; i++ ) { + c = Table[data[i]]; + + ecc[0] ^= c; + if ( c & 0x80 ) { + ecc[1] ^= ~i; + ecc[2] ^= i; + } + } + ecc[0] = ~ecc[0]; + ecc[0] &= 0x77; + + ecc[1] = ~ecc[1]; + ecc[1] &= 0x7f; + + ecc[2] = ~ecc[2]; + ecc[2] &= 0x7f; + + return; +} + +FolderMemoryCardAggregator::FolderMemoryCardAggregator() { + for ( uint i = 0; i < totalCardSlots; ++i ) { + m_cards[i].SetSlot( i ); + } +} + +void FolderMemoryCardAggregator::Open() { + for ( int i = 0; i < totalCardSlots; ++i ) { + m_cards[i].Open(); + } +} + +void FolderMemoryCardAggregator::Close() { + for ( int i = 0; i < totalCardSlots; ++i ) { + m_cards[i].Close(); + } +} + +s32 FolderMemoryCardAggregator::IsPresent( uint slot ) { + return m_cards[slot].IsPresent(); +} + +void FolderMemoryCardAggregator::GetSizeInfo( uint slot, PS2E_McdSizeInfo& outways ) { + m_cards[slot].GetSizeInfo( outways ); +} + +bool FolderMemoryCardAggregator::IsPSX( uint slot ) { + return m_cards[slot].IsPSX(); +} + +s32 FolderMemoryCardAggregator::Read( uint slot, u8 *dest, u32 adr, int size ) { + return m_cards[slot].Read( dest, adr, size ); +} + +s32 FolderMemoryCardAggregator::Save( uint slot, const u8 *src, u32 adr, int size ) { + return m_cards[slot].Save( src, adr, size ); +} + +s32 FolderMemoryCardAggregator::EraseBlock( uint slot, u32 adr ) { + return m_cards[slot].EraseBlock( adr ); +} + +u64 FolderMemoryCardAggregator::GetCRC( uint slot ) { + return m_cards[slot].GetCRC(); +} + +void FolderMemoryCardAggregator::NextFrame( uint slot ) { + m_cards[slot].NextFrame(); +} + + diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h new file mode 100644 index 000000000..dfdc769e4 --- /dev/null +++ b/pcsx2/gui/MemoryCardFolder.h @@ -0,0 +1,313 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2010 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "PluginCallbacks.h" + +// -------------------------------------------------------------------------------------- +// Superblock Header Struct +// -------------------------------------------------------------------------------------- +#pragma pack(push, 1) +struct superblock { + char magic[28]; // 0x00 + char version[12]; // 0x1c + u16 page_len; // 0x28 + u16 pages_per_cluster; // 0x2a + u16 pages_per_block; // 0x2c + u16 unused; // 0x2e + u32 clusters_per_card; // 0x30 + u32 alloc_offset; // 0x34 + u32 alloc_end; // 0x38 + u32 rootdir_cluster; // 0x3c + u32 backup_block1; // 0x40 + u32 backup_block2; // 0x44 + u64 padding0x48; // 0x48 + u32 ifc_list[32]; // 0x50 + u32 bad_block_list[32]; // 0xd0 + u8 card_type; // 0x150 + u8 card_flags; // 0x151 +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct MemoryCardFileEntryDateTime { + u8 unused; + u8 second; + u8 minute; + u8 hour; + u8 day; + u8 month; + u16 year; +}; +#pragma pack(pop) + +// -------------------------------------------------------------------------------------- +// MemoryCardFileEntry +// -------------------------------------------------------------------------------------- +// Structure for directory and file relationships as stored on memory cards +#pragma pack(push, 1) +struct MemoryCardFileEntry { + union { + struct MemoryCardFileEntryData { + u32 mode; + u32 length; // number of bytes for file, number of files for dir + union { + MemoryCardFileEntryDateTime data; + u64 value; + u8 raw[8]; + } timeCreated; + u32 cluster; // cluster the start of referred file or folder can be found in + u32 dirEntry; // parent directory entry number, only used if "." entry of subdir + union { + MemoryCardFileEntryDateTime data; + u64 value; + u8 raw[8]; + } timeModified; + u32 attr; + u8 padding[0x1C]; + u8 name[0x20]; + u8 unused[0x1A0]; + } data; + u8 raw[0x200]; + } entry; + + bool IsFile() { return !!( entry.data.mode & 0x0010 ); } + bool IsDir() { return !!( entry.data.mode & 0x0020 ); } + bool IsUsed() { return !!( entry.data.mode & 0x8000 ); } + bool IsValid() { return entry.data.mode != 0xFFFFFFFF; } +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct MemoryCardFileEntryCluster { + MemoryCardFileEntry entries[2]; +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct MemoryCardPage { + static const int PageSize = 0x200; + u8 raw[PageSize]; +}; +#pragma pack(pop) + +// -------------------------------------------------------------------------------------- +// FolderMemoryCard +// -------------------------------------------------------------------------------------- +// Fakes a memory card using a regular folder/file structure in the host file system +class FolderMemoryCard { +protected: + wxFileName folderName; + + // a few constants so we could in theory change the memory card size without too much effort + static const int IndirectFatClusterCount = 1; // should be 32 but only 1 is ever used + static const int PageSize = MemoryCardPage::PageSize; + static const int ClusterSize = PageSize * 2; + static const int BlockSize = ClusterSize * 8; + static const int EccSize = 0x10; + static const int PageSizeRaw = PageSize + EccSize; + static const int ClusterSizeRaw = PageSizeRaw * 2; + static const int BlockSizeRaw = ClusterSizeRaw * 8; + static const int TotalPages = 0x4000; + static const int TotalClusters = TotalPages / 2; + static const int TotalBlocks = TotalClusters / 8; + + static const int FramesAfterWriteUntilFlush = 60; + + union superBlockUnion { + superblock data; + u8 raw[BlockSize]; + } m_superBlock; + union indirectFatUnion { + u32 data[IndirectFatClusterCount][ClusterSize / 4]; + u8 raw[IndirectFatClusterCount][ClusterSize]; + } m_indirectFat; + union fatUnion { + u32 data[IndirectFatClusterCount][ClusterSize / 4][ClusterSize / 4]; + u8 raw[IndirectFatClusterCount][ClusterSize / 4][ClusterSize]; + } m_fat; + u8 m_backupBlock1[BlockSize]; + u8 m_backupBlock2[BlockSize]; + + std::map m_fileEntryDict; + + // holds a copy of modified areas of the memory card, in page-sized chunks + std::map m_cache; + + uint m_slot; + bool m_isEnabled; + u64 m_timeLastWritten; + int m_framesUntilFlush; + +public: + FolderMemoryCard(); + virtual ~FolderMemoryCard() throw() {} + + void Lock(); + void Unlock(); + + void Open(); + void Close(); + + s32 IsPresent(); + void GetSizeInfo( PS2E_McdSizeInfo& outways ); + bool IsPSX(); + s32 Read( u8 *dest, u32 adr, int size ); + s32 Save( const u8 *src, u32 adr, int size ); + s32 EraseBlock( u32 adr ); + u64 GetCRC(); + + void SetSlot( uint slot ); + + // called once per frame, used for flushing data after FramesAfterWriteUntilFlush frames of no writes + void NextFrame(); + + static void CalculateECC( u8* ecc, const u8* data ); + +protected: + // initializes memory card data, as if it was fresh from the factory + void InitializeInternalData(); + + bool IsFormatted(); + + // returns the in-memory address of data the given memory card adr corresponds to + // returns nullptr if adr corresponds to a folder or file entry + u8* GetSystemBlockPointer( const u32 adr ); + + // returns in-memory address of file or directory metadata searchCluster corresponds to + // returns nullptr if searchCluster contains something else + // originally call by passing: + // - currentCluster: the root directory cluster as indicated in the superblock + // - searchCluster: the cluster that is being accessed, relative to alloc_offset in the superblock + // - entryNumber: page of cluster + // - offset: offset of page + u8* GetFileEntryPointer( const u32 currentCluster, const u32 searchCluster, const u32 entryNumber, const u32 offset ); + + // returns file entry of the file at the given searchCluster + // the passed fileName will be filled with a path to the file being accessed + // returns nullptr if searchCluster contains no file + // call by passing: + // - currentCluster: the root directory cluster as indicated in the superblock + // - searchCluster: the cluster that is being accessed, relative to alloc_offset in the superblock + // - fileName: wxFileName of the root directory of the memory card folder in the host file system (filename part doesn't matter) + // - originalDirCount: the point in fileName where to insert the found folder path, usually fileName->GetDirCount() + // - outClusterNumber: the cluster's sequential number of the file will be written to this pointer, + // which can be used to calculate the in-file offset of the address being accessed + MemoryCardFileEntry* GetFileEntryFromFileDataCluster( const u32 currentCluster, const u32 searchCluster, wxFileName* fileName, const size_t originalDirCount, u32* outClusterNumber ); + + + // loads files and folders from the host file system if a superblock exists in the root directory + void LoadMemoryCardData(); + + // creates the FAT and indirect FAT + void CreateFat(); + + // creates file entries for the root directory + void CreateRootDir(); + + + // returns the system cluster past the highest used one (will be the lowest free one under normal use) + // this is used for creating the FAT, don't call otherwise unless you know exactly what you're doing + u32 GetFreeSystemCluster(); + + // returns the lowest unused data cluster, relative to alloc_offset in the superblock + // returns 0xFFFFFFFFu when the memory card is full + u32 GetFreeDataCluster(); + + // returns the amount of unused data clusters + u32 GetAmountFreeDataClusters(); + + // returns the final cluster of the file or directory which is (partially) stored in the given cluster + u32 GetLastClusterOfData( const u32 cluster ); + + u64 ConvertToMemoryCardTimestamp( const wxDateTime& time ); + + + // creates and returns a new file entry in the given directory entry, ready to be filled + // returns nullptr when the memory card is full + MemoryCardFileEntry* AppendFileEntryToDir( MemoryCardFileEntry* const dirEntry ); + + // adds a folder in the host file system to the memory card, including all files and subdirectories + // - dirEntry: the entry of the directory in the parent directory, or the root "." entry + // - dirPath: the full path to the directory in the host file system + bool AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath ); + + // adds a file in the host file sytem to the memory card + // - dirEntry: the entry of the directory in the parent directory, or the root "." entry + // - dirPath: the full path to the directory containing the file in the host file system + // - fileName: the name of the file, without path + bool AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName ); + + + bool ReadFromFile( u8 *dest, u32 adr, u32 dataLength ); + bool WriteToFile( const u8* src, u32 adr, u32 dataLength ); + + + // flush the whole cache to the internal data and/or host file system + void Flush(); + + // flush a single page of the cache to the internal data and/or host file system + void Flush( const u32 page ); + + // flush a directory's file entries and all its subdirectories to the internal data + void FlushFileEntries( const u32 dirCluster, const u32 remainingFiles ); + + // write data as Save() normally would, but ignore the cache; used for flushing + s32 WriteWithoutCache( const u8 *src, u32 adr, int size ); + + void SetTimeLastWrittenToNow(); + + wxString GetDisabledMessage( uint slot ) const { + return wxsFormat( pxE( L"The PS2-slot %d has been automatically disabled. You can correct the problem\nand re-enable it at any time using Config:Memory cards from the main menu." + ), slot//TODO: translate internal slot index to human-readable slot description + ); + } + wxString GetCardFullMessage( const wxString& filePath ) const { + return wxsFormat( pxE( L"(FolderMcd) Memory Card is full, could not add: %s" ), WX_STR( filePath ) ); + } +}; + +// -------------------------------------------------------------------------------------- +// FolderMemoryCardAggregator +// -------------------------------------------------------------------------------------- +// Forwards the API's requests for specific memory card slots to the correct FolderMemoryCard. +class FolderMemoryCardAggregator { +protected: + static const int totalCardSlots = 8; + FolderMemoryCard m_cards[totalCardSlots]; + +public: + FolderMemoryCardAggregator(); + virtual ~FolderMemoryCardAggregator() throw( ) {} + + void Open(); + void Close(); + + s32 IsPresent( uint slot ); + void GetSizeInfo( uint slot, PS2E_McdSizeInfo& outways ); + bool IsPSX( uint slot ); + s32 Read( uint slot, u8 *dest, u32 adr, int size ); + s32 Save( uint slot, const u8 *src, u32 adr, int size ); + s32 EraseBlock( uint slot, u32 adr ); + u64 GetCRC( uint slot ); + void NextFrame( uint slot ); +}; diff --git a/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj b/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj index af0c8edad..1e469df16 100644 --- a/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj +++ b/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj @@ -636,6 +636,7 @@ + diff --git a/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj.filters b/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj.filters index f8a6210b0..80e1532c2 100644 --- a/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj.filters +++ b/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj.filters @@ -635,6 +635,9 @@ AppHost + + AppHost + AppHost diff --git a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj index b9b4b0432..5d3c284c3 100644 --- a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj +++ b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj @@ -636,6 +636,7 @@ + diff --git a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters index 6c97171bb..065beb5da 100644 --- a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters +++ b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters @@ -635,6 +635,9 @@ AppHost + + AppHost + AppHost From f15c07653c636f63fe4504206121efec277845fa Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 9 May 2015 03:22:17 +0200 Subject: [PATCH 02/44] MemoryCard: Add support for folder memcards in GUI and make both implementation function side-by-side. --- pcsx2/Counters.cpp | 2 - pcsx2/gui/AppConfig.cpp | 8 + pcsx2/gui/AppConfig.h | 9 ++ pcsx2/gui/Dialogs/CreateMemoryCardDialog.cpp | 42 +++-- pcsx2/gui/MemoryCardFile.cpp | 116 ++++++++++++-- pcsx2/gui/MemoryCardFile.h | 3 - pcsx2/gui/Panels/MemoryCardListPanel.cpp | 159 ++++++++++++++++--- pcsx2/gui/Panels/MemoryCardListView.cpp | 2 +- pcsx2/gui/Panels/MemoryCardPanels.h | 1 + 9 files changed, 281 insertions(+), 61 deletions(-) diff --git a/pcsx2/Counters.cpp b/pcsx2/Counters.cpp index cc9c1c6a3..387ab368d 100644 --- a/pcsx2/Counters.cpp +++ b/pcsx2/Counters.cpp @@ -431,10 +431,8 @@ static __fi void VSyncEnd(u32 sCycle) psxVBlankEnd(); // psxCounters vBlank End if (gates) rcntEndGate(true, sCycle); // Counters End Gate Code -#ifdef MEMORYCARD_USE_FOLDER // FolderMemoryCard needs information on how much time has passed since the last write sioNextFrame(); -#endif frameLimit(); // limit FPS diff --git a/pcsx2/gui/AppConfig.cpp b/pcsx2/gui/AppConfig.cpp index 3e057dad1..16c7b9c5d 100644 --- a/pcsx2/gui/AppConfig.cpp +++ b/pcsx2/gui/AppConfig.cpp @@ -611,6 +611,10 @@ void AppConfig::LoadSaveMemcards( IniInterface& ini ) Mcd[slot].Enabled, Mcd[slot].Enabled ); ini.Entry( pxsFmt( L"Slot%u_Filename", slot+1 ), Mcd[slot].Filename, Mcd[slot].Filename ); + int type = (int)Mcd[slot].Type; + ini.Entry( pxsFmt( L"Slot%u_Type", slot + 1 ), + type, (int)MemoryCardType::MemoryCard_File ); + Mcd[slot].Type = (MemoryCardType)type; } for( uint slot=2; slot<8; ++slot ) @@ -622,6 +626,10 @@ void AppConfig::LoadSaveMemcards( IniInterface& ini ) Mcd[slot].Enabled, Mcd[slot].Enabled ); ini.Entry( pxsFmt( L"Multitap%u_Slot%u_Filename", mtport, mtslot ), Mcd[slot].Filename, Mcd[slot].Filename ); + int type = (int)Mcd[slot].Type; + ini.Entry( pxsFmt( L"Multitap%u_Slot%u_Type", mtport, mtslot ), + type, (int)MemoryCardType::MemoryCard_File ); + Mcd[slot].Type = (MemoryCardType)type; } } diff --git a/pcsx2/gui/AppConfig.h b/pcsx2/gui/AppConfig.h index 81eccf449..260a76b16 100644 --- a/pcsx2/gui/AppConfig.h +++ b/pcsx2/gui/AppConfig.h @@ -98,6 +98,14 @@ enum AspectRatioType AspectRatio_MaxCount }; +enum MemoryCardType +{ + MemoryCard_None, + MemoryCard_File, + MemoryCard_Folder, + MemoryCard_MaxCount +}; + // ===================================================================================================== // Pcsx2 Application Configuration. // ===================================================================================================== @@ -182,6 +190,7 @@ public: { wxFileName Filename; // user-configured location of this memory card bool Enabled; // memory card enabled (if false, memcard will not show up in-game) + MemoryCardType Type; // the memory card implementation that should be used }; // ------------------------------------------------------------------------ diff --git a/pcsx2/gui/Dialogs/CreateMemoryCardDialog.cpp b/pcsx2/gui/Dialogs/CreateMemoryCardDialog.cpp index a94886067..5e2685be4 100644 --- a/pcsx2/gui/Dialogs/CreateMemoryCardDialog.cpp +++ b/pcsx2/gui/Dialogs/CreateMemoryCardDialog.cpp @@ -153,17 +153,31 @@ void Dialogs::CreateMemoryCardDialog::OnOk_Click( wxCommandEvent& evt ) return; } - wxString fullPath=(m_mcdpath + composedName).GetFullPath(); - if( !CreateIt( - fullPath, - m_radio_CardSize ? m_radio_CardSize->SelectedItem().SomeInt : 8 - ) ) - { - Msgbox::Alert( - _("Error: The memory card could not be created."), - _("Create memory card") - ); - return; + wxString fullPath = ( m_mcdpath + composedName ).GetFullPath(); + if ( m_radio_CardSize && m_radio_CardSize->SelectedItem().SomeInt == 0 ) { + // user selected to create a folder memory card + if ( !wxFileName::Mkdir( fullPath ) ) { + Msgbox::Alert( + _( "Error: The directory for the memory card could not be created." ), + _( "Create memory card" ) + ); + } else { + // also create an empty superblock so we can recognize memory card folders based on if they have a superblock + wxFFile superblock( wxFileName( fullPath, L"_pcsx2_superblock" ).GetFullPath(), L"wb" ); + superblock.Close(); + } + } else { + // otherwise create a file + if ( !CreateIt( + fullPath, + m_radio_CardSize ? m_radio_CardSize->SelectedItem().SomeInt : 8 + ) ) { + Msgbox::Alert( + _( "Error: The memory card could not be created." ), + _( "Create memory card" ) + ); + return; + } } result_createdMcdFilename = composedName; @@ -205,7 +219,11 @@ void Dialogs::CreateMemoryCardDialog::CreateControls() RadioPanelItem(_("64 MB"), _("Low compatibility warning: Yes it's very big, but may not work with many games.")) . SetToolTip(_t("Use at your own risk. Erratic memory card behavior is possible (though unlikely).")) - . SetInt(64) + . SetInt(64), + + RadioPanelItem(_("Folder [experimental]"), _("Store memory card contents in the host filesystem instead of a file.")) + . SetToolTip(_t("Automatically manages memory card contents so that the console only sees files related to the currently running software. Allows you to drag-and-drop files in and out of the memory card with your standard file explorer. This is still experimental, so use at your own risk!")) + . SetInt(0) }; m_radio_CardSize = new pxRadioPanel( this, tbl_CardSizes ); diff --git a/pcsx2/gui/MemoryCardFile.cpp b/pcsx2/gui/MemoryCardFile.cpp index e9dfd7486..bd369c275 100644 --- a/pcsx2/gui/MemoryCardFile.cpp +++ b/pcsx2/gui/MemoryCardFile.cpp @@ -74,6 +74,8 @@ public: s32 Save ( uint slot, const u8 *src, u32 adr, int size ); s32 EraseBlock ( uint slot, u32 adr ); u64 GetCRC ( uint slot ); + + void NextFrame( uint slot ); protected: bool Seek( wxFFile& f, u32 adr ); @@ -171,7 +173,12 @@ void FileMemoryCard::Open() cont = true; } - Console.WriteLn( cont ? Color_Gray : Color_Green, L"McdSlot %u: " + str, slot ); + if ( g_Conf->Mcd[slot].Type != MemoryCardType::MemoryCard_File ) { + str = L"[is not memcard file]"; + cont = true; + } + + Console.WriteLn( cont ? Color_Gray : Color_Green, L"McdSlot %u [File]: " + str, slot ); if( cont ) continue; const wxULongLong fsz = fname.GetSize(); @@ -401,18 +408,20 @@ u64 FileMemoryCard::GetCRC( uint slot ) return retval; } +void FileMemoryCard::NextFrame( uint slot ) +{ + +} + // -------------------------------------------------------------------------------------- // MemoryCard Component API Bindings // -------------------------------------------------------------------------------------- struct Component_FileMcd { - PS2E_ComponentAPI_Mcd api; // callbacks the plugin provides back to the emulator -#ifdef MEMORYCARD_USE_FOLDER - FolderMemoryCardAggregator impl; -#else - FileMemoryCard impl; // class-based implementations we refer to when API is invoked -#endif + PS2E_ComponentAPI_Mcd api; // callbacks the plugin provides back to the emulator + FileMemoryCard impl; // class-based implementations we refer to when API is invoked + FolderMemoryCardAggregator implFolder; Component_FileMcd(); }; @@ -427,52 +436,120 @@ uint FileMcd_ConvertToSlot( uint port, uint slot ) static void PS2E_CALLBACK FileMcd_EmuOpen( PS2E_THISPTR thisptr, const PS2E_SessionInfo *session ) { thisptr->impl.Open(); + thisptr->implFolder.Open(); } static void PS2E_CALLBACK FileMcd_EmuClose( PS2E_THISPTR thisptr ) { + thisptr->implFolder.Close(); thisptr->impl.Close(); } static s32 PS2E_CALLBACK FileMcd_IsPresent( PS2E_THISPTR thisptr, uint port, uint slot ) { - return thisptr->impl.IsPresent( FileMcd_ConvertToSlot( port, slot ) ); + const uint combinedSlot = FileMcd_ConvertToSlot( port, slot ); + switch ( g_Conf->Mcd[combinedSlot].Type ) { + case MemoryCardType::MemoryCard_File: + return thisptr->impl.IsPresent( combinedSlot ); + case MemoryCardType::MemoryCard_Folder: + return thisptr->implFolder.IsPresent( combinedSlot ); + default: + return false; + } } static void PS2E_CALLBACK FileMcd_GetSizeInfo( PS2E_THISPTR thisptr, uint port, uint slot, PS2E_McdSizeInfo* outways ) { - thisptr->impl.GetSizeInfo( FileMcd_ConvertToSlot( port, slot ), *outways ); + const uint combinedSlot = FileMcd_ConvertToSlot( port, slot ); + switch ( g_Conf->Mcd[combinedSlot].Type ) { + case MemoryCardType::MemoryCard_File: + thisptr->impl.GetSizeInfo( combinedSlot, *outways ); + break; + case MemoryCardType::MemoryCard_Folder: + thisptr->implFolder.GetSizeInfo( combinedSlot, *outways ); + break; + default: + return; + } } static bool PS2E_CALLBACK FileMcd_IsPSX( PS2E_THISPTR thisptr, uint port, uint slot ) { - return thisptr->impl.IsPSX( FileMcd_ConvertToSlot( port, slot ) ); + const uint combinedSlot = FileMcd_ConvertToSlot( port, slot ); + switch ( g_Conf->Mcd[combinedSlot].Type ) { + case MemoryCardType::MemoryCard_File: + return thisptr->impl.IsPSX( combinedSlot ); + case MemoryCardType::MemoryCard_Folder: + return thisptr->implFolder.IsPSX( combinedSlot ); + default: + return false; + } } static s32 PS2E_CALLBACK FileMcd_Read( PS2E_THISPTR thisptr, uint port, uint slot, u8 *dest, u32 adr, int size ) { - return thisptr->impl.Read( FileMcd_ConvertToSlot( port, slot ), dest, adr, size ); + const uint combinedSlot = FileMcd_ConvertToSlot( port, slot ); + switch ( g_Conf->Mcd[combinedSlot].Type ) { + case MemoryCardType::MemoryCard_File: + return thisptr->impl.Read( combinedSlot, dest, adr, size ); + case MemoryCardType::MemoryCard_Folder: + return thisptr->implFolder.Read( combinedSlot, dest, adr, size ); + default: + return 0; + } } static s32 PS2E_CALLBACK FileMcd_Save( PS2E_THISPTR thisptr, uint port, uint slot, const u8 *src, u32 adr, int size ) { - return thisptr->impl.Save( FileMcd_ConvertToSlot( port, slot ), src, adr, size ); + const uint combinedSlot = FileMcd_ConvertToSlot( port, slot ); + switch ( g_Conf->Mcd[combinedSlot].Type ) { + case MemoryCardType::MemoryCard_File: + return thisptr->impl.Save( combinedSlot, src, adr, size ); + case MemoryCardType::MemoryCard_Folder: + return thisptr->implFolder.Save( combinedSlot, src, adr, size ); + default: + return 0; + } } static s32 PS2E_CALLBACK FileMcd_EraseBlock( PS2E_THISPTR thisptr, uint port, uint slot, u32 adr ) { - return thisptr->impl.EraseBlock( FileMcd_ConvertToSlot( port, slot ), adr ); + const uint combinedSlot = FileMcd_ConvertToSlot( port, slot ); + switch ( g_Conf->Mcd[combinedSlot].Type ) { + case MemoryCardType::MemoryCard_File: + return thisptr->impl.EraseBlock( combinedSlot, adr ); + case MemoryCardType::MemoryCard_Folder: + return thisptr->implFolder.EraseBlock( combinedSlot, adr ); + default: + return 0; + } } static u64 PS2E_CALLBACK FileMcd_GetCRC( PS2E_THISPTR thisptr, uint port, uint slot ) { - return thisptr->impl.GetCRC( FileMcd_ConvertToSlot( port, slot ) ); + const uint combinedSlot = FileMcd_ConvertToSlot( port, slot ); + switch ( g_Conf->Mcd[combinedSlot].Type ) { + case MemoryCardType::MemoryCard_File: + return thisptr->impl.GetCRC( combinedSlot ); + case MemoryCardType::MemoryCard_Folder: + return thisptr->implFolder.GetCRC( combinedSlot ); + default: + return 0; + } } static void PS2E_CALLBACK FileMcd_NextFrame( PS2E_THISPTR thisptr, uint port, uint slot ) { -#ifdef MEMORYCARD_USE_FOLDER - thisptr->impl.NextFrame( FileMcd_ConvertToSlot( port, slot ) ); -#endif + const uint combinedSlot = FileMcd_ConvertToSlot( port, slot ); + switch ( g_Conf->Mcd[combinedSlot].Type ) { + case MemoryCardType::MemoryCard_File: + thisptr->impl.NextFrame( combinedSlot ); + break; + case MemoryCardType::MemoryCard_Folder: + thisptr->implFolder.NextFrame( combinedSlot ); + break; + default: + return; + } } Component_FileMcd::Component_FileMcd() @@ -586,6 +663,11 @@ bool isValidNewFilename( wxString filenameStringToTest, wxDirName atBasePath, wx out_errorMessage = _("File name already exists"); return false; } + if ( wxDirExists( (atBasePath + wxFileName(filenameStringToTest)).GetFullPath() )) + { + out_errorMessage = _( "File name already exists" ); + return false; + } wxFile fp; if( !fp.Create( (atBasePath + wxFileName(filenameStringToTest)).GetFullPath() )) diff --git a/pcsx2/gui/MemoryCardFile.h b/pcsx2/gui/MemoryCardFile.h index 387d6887b..9f27c3f93 100644 --- a/pcsx2/gui/MemoryCardFile.h +++ b/pcsx2/gui/MemoryCardFile.h @@ -15,9 +15,6 @@ #pragma once -// define this to use the FolderMemoryCard implementation instead of the regular FileMemoryCard one -//#define MEMORYCARD_USE_FOLDER - // NOTICE! This file is intended as a temporary placebo only, until such time that the // memorycard system is properly extracted into a plugin system (which would make it a // separate project file). diff --git a/pcsx2/gui/Panels/MemoryCardListPanel.cpp b/pcsx2/gui/Panels/MemoryCardListPanel.cpp index 05ede4403..ca6009277 100644 --- a/pcsx2/gui/Panels/MemoryCardListPanel.cpp +++ b/pcsx2/gui/Panels/MemoryCardListPanel.cpp @@ -54,19 +54,47 @@ bool EnumerateMemoryCard( McdSlotItem& dest, const wxFileName& filename, const w { dest.IsFormatted = false; dest.IsPresent = false; + dest.Type = MemoryCardType::MemoryCard_None; const wxString fullpath( filename.GetFullPath() ); - if( !filename.FileExists() ) return false; - //DevCon.WriteLn( fullpath ); - wxFFile mcdFile( fullpath ); - if( !mcdFile.IsOpened() ) return false; // wx should log the error for us. + if ( filename.FileExists() ) { + // might be a memory card file + wxFFile mcdFile( fullpath ); + if ( !mcdFile.IsOpened() ) { return false; } // wx should log the error for us. - wxFileOffset length = mcdFile.Length(); + wxFileOffset length = mcdFile.Length(); - if( length < (1024*528) && length != 0x20000 ) - { - Console.Warning( "... MemoryCard appears to be truncated. Ignoring." ); + if( length < (1024*528) && length != 0x20000 ) + { + Console.Warning( "... MemoryCard appears to be truncated. Ignoring." ); + return false; + } + + dest.SizeInMB = (uint)( length / ( 1024 * 528 * 2 ) ); + + if ( length == 0x20000 ) { + dest.IsPSX = true; // PSX memcard; + dest.SizeInMB = 1; // MegaBIT + } + + dest.Type = MemoryCardType::MemoryCard_File; + dest.IsFormatted = IsMcdFormatted( mcdFile ); + filename.GetTimes( NULL, &dest.DateModified, &dest.DateCreated ); + } else if ( filename.DirExists() ) { + // might be a memory card folder + wxFileName superBlockFileName( fullpath, L"_pcsx2_superblock" ); + if ( !superBlockFileName.FileExists() ) { return false; } + wxFFile mcdFile( superBlockFileName.GetFullPath() ); + if ( !mcdFile.IsOpened() ) { return false; } + + dest.SizeInMB = 0; + + dest.Type = MemoryCardType::MemoryCard_Folder; + dest.IsFormatted = IsMcdFormatted( mcdFile ); + superBlockFileName.GetTimes( NULL, &dest.DateModified, &dest.DateCreated ); + } else { + // is neither return false; } @@ -74,18 +102,7 @@ bool EnumerateMemoryCard( McdSlotItem& dest, const wxFileName& filename, const w dest.Filename = filename; if( filename.GetFullPath() == (basePath+filename.GetFullName()).GetFullPath() ) dest.Filename = filename.GetFullName(); - - dest.SizeInMB = (uint)(length / (1024 * 528 * 2)); - - if(length == 0x20000) - { - dest.IsPSX = true; // PSX memcard; - dest.SizeInMB = 1; // MegaBIT - } - dest.IsFormatted = IsMcdFormatted( mcdFile ); - filename.GetTimes( NULL, &dest.DateModified, &dest.DateCreated ); - return true; } @@ -595,6 +612,7 @@ void Panels::MemoryCardListPanel_Simple::Apply() Console.WriteLn( L"Apply Memory cards:" ); for( uint slot=0; slot<8; ++slot ) { + g_Conf->Mcd[slot].Type = m_Cards[slot].Type; g_Conf->Mcd[slot].Enabled = m_Cards[slot].IsEnabled && m_Cards[slot].IsPresent; if (m_Cards[slot].IsPresent) g_Conf->Mcd[slot].Filename = m_Cards[slot].Filename; @@ -623,7 +641,7 @@ void Panels::MemoryCardListPanel_Simple::AppStatusEvent_OnSettingsApplied() //automatically create the enabled but non-existing file such that it can be managed (else will get created anyway on boot) wxString targetFile = (GetMcdPath() + m_Cards[slot].Filename.GetFullName()).GetFullPath(); - if ( m_Cards[slot].IsEnabled && !wxFileExists( targetFile ) ) + if ( m_Cards[slot].IsEnabled && !( wxFileExists( targetFile ) || wxDirExists( targetFile ) ) ) { wxString errMsg; if (isValidNewFilename(m_Cards[slot].Filename.GetFullName(), GetMcdPath(), errMsg, 5)) @@ -639,7 +657,7 @@ void Panels::MemoryCardListPanel_Simple::AppStatusEvent_OnSettingsApplied() } } - if ( !m_Cards[slot].IsEnabled || !wxFileExists( targetFile ) ) + if ( !m_Cards[slot].IsEnabled || !( wxFileExists( targetFile ) || wxDirExists( targetFile ) ) ) { m_Cards[slot].IsEnabled = false; m_Cards[slot].IsPresent = false; @@ -728,6 +746,72 @@ void Panels::MemoryCardListPanel_Simple::UiCreateNewCard( McdSlotItem& card ) closed_core.AllowResume(); } +bool CopyDirectory( const wxString& from, const wxString& to ) { + wxDir src( from ); + if ( !src.IsOpened() ) { + return false; + } + + wxMkdir( to ); + wxDir dst( to ); + if ( !dst.IsOpened() ) { + return false; + } + + wxString filename; + + // copy directories + if ( src.GetFirst( &filename, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN ) ) { + do { + if ( !CopyDirectory( wxFileName( from, filename ).GetFullPath(), wxFileName( to, filename ).GetFullPath() ) ) { + return false; + } + } while ( src.GetNext( &filename ) ); + } + + // copy files + if ( src.GetFirst( &filename, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN ) ) { + do { + if ( !wxCopyFile( wxFileName( from, filename ).GetFullPath(), wxFileName( to, filename ).GetFullPath() ) ) { + return false; + } + } while ( src.GetNext( &filename ) ); + } + + return true; +} + +bool RemoveDirectory( const wxString& dirname ) { + { + wxDir dir( dirname ); + if ( !dir.IsOpened() ) { + return false; + } + + wxString filename; + + // delete subdirs recursively + if ( dir.GetFirst( &filename, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN ) ) { + do { + if ( !RemoveDirectory( wxFileName( dirname, filename ).GetFullPath() ) ) { + return false; + } + } while ( dir.GetNext( &filename ) ); + } + + // delete files + if ( dir.GetFirst( &filename, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN ) ) { + do { + if ( !wxRemoveFile( wxFileName( dirname, filename ).GetFullPath() ) ) { + return false; + } + } while ( dir.GetNext( &filename ) ); + } + } + + // oddly enough this has different results compared to the more sensible dirname.Rmdir(), don't change! + return wxFileName::Rmdir( dirname ); +} void Panels::MemoryCardListPanel_Simple::UiDeleteCard( McdSlotItem& card ) { @@ -757,7 +841,12 @@ void Panels::MemoryCardListPanel_Simple::UiDeleteCard( McdSlotItem& card ) card.IsEnabled=false; Apply(); - wxRemoveFile( fullpath.GetFullPath() ); + + if ( fullpath.FileExists() ) { + wxRemoveFile( fullpath.GetFullPath() ); + } else { + RemoveDirectory( fullpath.GetFullPath() ); + } RefreshSelections(); closed_core.AllowResume(); @@ -817,7 +906,8 @@ bool Panels::MemoryCardListPanel_Simple::UiDuplicateCard(McdSlotItem& src, McdSl ScopedBusyCursor doh( Cursor_ReallyBusy ); ScopedCoreThreadClose closed_core; - if( !wxCopyFile( srcfile.GetFullPath(), destfile.GetFullPath(), true ) ) + if( !( ( srcfile.FileExists() && wxCopyFile( srcfile.GetFullPath(), destfile.GetFullPath(), true ) ) + || ( !srcfile.FileExists() && CopyDirectory( srcfile.GetFullPath(), destfile.GetFullPath() ) ) ) ) { wxString heading; heading.Printf( pxE( L"Failed: Destination memory card '%s' is in use." ), @@ -1106,9 +1196,26 @@ void Panels::MemoryCardListPanel_Simple::ReadFilesAtMcdFolder(){ wxArrayString memcardList; - wxDir::GetAllFiles(m_FolderPicker->GetPath().ToString(), &memcardList, L"*.ps2", wxDIR_FILES); - wxDir::GetAllFiles(m_FolderPicker->GetPath().ToString(), &memcardList, L"*.mcd", wxDIR_FILES); - wxDir::GetAllFiles(m_FolderPicker->GetPath().ToString(), &memcardList, L"*.mcr", wxDIR_FILES); + wxString filename = m_FolderPicker->GetPath().ToString(); + wxDir memcardDir( filename ); + if ( memcardDir.IsOpened() ) { + // add memory card files + wxDir::GetAllFiles( filename, &memcardList, L"*.ps2", wxDIR_FILES ); + wxDir::GetAllFiles( filename, &memcardList, L"*.mcd", wxDIR_FILES ); + wxDir::GetAllFiles( filename, &memcardList, L"*.mcr", wxDIR_FILES ); + + // add memory card folders + wxString dirname; + if ( memcardDir.GetFirst( &dirname, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN ) ) { + do { + wxFileName superBlockFileName( wxFileName( filename, dirname ).GetFullPath(), L"_pcsx2_superblock" ); + if ( superBlockFileName.FileExists() ) { + memcardList.Add( superBlockFileName.GetPath() ); + } + } while ( memcardDir.GetNext( &dirname ) ); + } + } + for(uint i = 0; i < memcardList.size(); i++) { McdSlotItem currentCardFile; diff --git a/pcsx2/gui/Panels/MemoryCardListView.cpp b/pcsx2/gui/Panels/MemoryCardListView.cpp index 5b7e89027..bb8aabe7f 100644 --- a/pcsx2/gui/Panels/MemoryCardListView.cpp +++ b/pcsx2/gui/Panels/MemoryCardListView.cpp @@ -156,7 +156,7 @@ wxString MemoryCardListView_Simple::OnGetItemText(long item, long column) const return prefix + res; } */ - case McdColS_Size: return prefix + ( !it.IsPresent ? L"" : (it.IsPSX? pxsFmt( L"%u MBit", it.SizeInMB ) : pxsFmt( L"%u MiB", it.SizeInMB )) ); + case McdColS_Size: return prefix + ( !it.IsPresent ? L"" : (it.IsPSX? pxsFmt( L"%u MBit", it.SizeInMB ) : ( it.SizeInMB > 0 ? pxsFmt( L"%u MiB", it.SizeInMB ) : L"Auto" ) ) ); case McdColS_Formatted: return prefix + ( !it.IsPresent ? L"" : ( it.IsFormatted ? _("Yes") : _("No")) ); case McdColS_Type: return prefix + ( !it.IsPresent ? L"" : ( it.IsPSX? _("PSX") : _("PS2")) ); case McdColS_DateModified: return prefix + ( !it.IsPresent ? L"" : it.DateModified.FormatDate() ); diff --git a/pcsx2/gui/Panels/MemoryCardPanels.h b/pcsx2/gui/Panels/MemoryCardPanels.h index 826ae3894..d4daf113b 100644 --- a/pcsx2/gui/Panels/MemoryCardPanels.h +++ b/pcsx2/gui/Panels/MemoryCardPanels.h @@ -40,6 +40,7 @@ struct McdSlotItem { int Slot; //0-7: internal slot. -1: unrelated to an internal slot (the rest of the files at the folder). bool IsPresent; //Whether or not a file is associated with this item (true/false when 0<=Slot<=7. Always true when Slot==-1) + MemoryCardType Type; //The implementation used for this memory card //Only meaningful when IsPresent==true (a file exists for this item): wxFileName Filename; // full pathname From 139e28988d19523533e66a07b5a16c922d56974f Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 9 May 2015 03:31:30 +0200 Subject: [PATCH 03/44] FolderMemoryCard: Store nonstandard file and folder metadata. Fixes issues with Star Ocean 3 battle trophies, and probably some other games. --- pcsx2/gui/MemoryCardFolder.cpp | 107 +++++++++++++++++++++++++++------ pcsx2/gui/MemoryCardFolder.h | 5 +- 2 files changed, 93 insertions(+), 19 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 5ac35e92d..aead262ee 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -273,14 +273,17 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS int entryNumber = 2; // include . and .. hasNext = dir.GetFirst( &fileName ); while ( hasNext ) { + if ( fileName.StartsWith( L"_pcsx2_" ) ) { + hasNext = dir.GetNext( &fileName ); + continue; + } + wxFileName fileInfo( dirPath, fileName ); bool isFile = wxFile::Exists( fileInfo.GetFullPath() ); if ( isFile ) { - if ( !fileName.StartsWith( L"_pcsx2_" ) ) { - if ( AddFile( dirEntry, dirPath, fileName ) ) { - ++entryNumber; - } + if ( AddFile( dirEntry, dirPath, fileName ) ) { + ++entryNumber; } } else { // make sure we have enough space on the memcard for the directory @@ -301,10 +304,21 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS // add entry for subdir in parent dir MemoryCardFileEntry* newDirEntry = AppendFileEntryToDir( dirEntry ); dirEntry->entry.data.length++; - newDirEntry->entry.data.mode = 0x8427; + + // set metadata + wxFileName metaFileName( dirPath, L"_pcsx2_meta_directory" ); + metaFileName.AppendDir( fileName ); + wxFFile metaFile; + if ( metaFileName.FileExists() && metaFile.Open( metaFileName.GetFullPath(), L"rb" ) ) { + metaFile.Read( &newDirEntry->entry.raw, 0x40 ); + metaFile.Close(); + } else { + newDirEntry->entry.data.mode = MemoryCardFileEntry::DefaultDirMode; + newDirEntry->entry.data.timeCreated.value = ConvertToMemoryCardTimestamp( creationTime ); + newDirEntry->entry.data.timeModified.value = ConvertToMemoryCardTimestamp( modificationTime ); + } + newDirEntry->entry.data.length = 2; - newDirEntry->entry.data.timeCreated.value = ConvertToMemoryCardTimestamp( creationTime ); - newDirEntry->entry.data.timeModified.value = ConvertToMemoryCardTimestamp( modificationTime ); strcpy( (char*)&newDirEntry->entry.data.name[0], fileName.mbc_str() ); // create new cluster for . and .. entries @@ -314,12 +328,12 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS MemoryCardFileEntryCluster* const subDirCluster = &m_fileEntryDict[newCluster]; memset( &subDirCluster->entries[0].entry.raw[0], 0x00, 0x200 ); - subDirCluster->entries[0].entry.data.mode = 0x8427; + subDirCluster->entries[0].entry.data.mode = MemoryCardFileEntry::DefaultDirMode; subDirCluster->entries[0].entry.data.dirEntry = entryNumber; subDirCluster->entries[0].entry.data.name[0] = '.'; memset( &subDirCluster->entries[1].entry.raw[0], 0x00, 0x200 ); - subDirCluster->entries[1].entry.data.mode = 0x8427; + subDirCluster->entries[1].entry.data.mode = MemoryCardFileEntry::DefaultDirMode; subDirCluster->entries[1].entry.data.name[0] = '.'; subDirCluster->entries[1].entry.data.name[1] = '.'; @@ -361,12 +375,22 @@ bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxStr wxDateTime creationTime, modificationTime; fileInfo.GetTimes( NULL, &modificationTime, &creationTime ); - // set file entry data + // set file entry metadata memset( &newFileEntry->entry.raw[0], 0x00, 0x200 ); - newFileEntry->entry.data.mode = 0x8497; + + wxFileName metaFileName( dirPath, fileName ); + metaFileName.AppendDir( L"_pcsx2_meta" ); + wxFFile metaFile; + if ( metaFileName.FileExists() && metaFile.Open( metaFileName.GetFullPath(), L"rb" ) ) { + metaFile.Read( &newFileEntry->entry.raw, 0x40 ); + metaFile.Close(); + } else { + newFileEntry->entry.data.mode = MemoryCardFileEntry::DefaultFileMode; + newFileEntry->entry.data.timeCreated.value = ConvertToMemoryCardTimestamp( creationTime ); + newFileEntry->entry.data.timeModified.value = ConvertToMemoryCardTimestamp( modificationTime ); + } + newFileEntry->entry.data.length = filesize; - newFileEntry->entry.data.timeCreated.value = ConvertToMemoryCardTimestamp( creationTime ); - newFileEntry->entry.data.timeModified.value = ConvertToMemoryCardTimestamp( modificationTime ); u32 fileDataStartingCluster = GetFreeDataCluster(); newFileEntry->entry.data.cluster = fileDataStartingCluster; strcpy( (char*)&newFileEntry->entry.data.name[0], fileName.mbc_str() ); @@ -721,7 +745,7 @@ void FolderMemoryCard::Flush( const u32 page ) { } } -void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remainingFiles ) { +void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remainingFiles, const wxString& dirPath ) { // flush the current cluster const u32 page = ( dirCluster + m_superBlock.data.alloc_offset ) * 2; Flush( page ); @@ -735,7 +759,28 @@ void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remaini if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() ) { const u32 cluster = entry->entry.data.cluster; if ( cluster > 0 ) { - FlushFileEntries( cluster, entry->entry.data.length ); + const wxString subDirName = wxString::FromAscii( (const char*)entry->entry.data.name ); + const wxString subDirPath = dirPath + L"/" + subDirName; + + // if this directory has nonstandard metadata, write that to the file system + wxFileName metaFileName( folderName.GetFullPath() + subDirPath + L"/_pcsx2_meta_directory" ); + if ( entry->entry.data.mode != MemoryCardFileEntry::DefaultDirMode || entry->entry.data.attr != 0 ) { + if ( !metaFileName.DirExists() ) { + metaFileName.Mkdir(); + } + wxFFile metaFile( metaFileName.GetFullPath(), L"wb" ); + if ( metaFile.IsOpened() ) { + metaFile.Write( entry->entry.raw, 0x40 ); + metaFile.Close(); + } + } else { + // if metadata is standard make sure to remove a possibly existing metadata file + if ( metaFileName.FileExists() ) { + wxRemoveFile( metaFileName.GetFullPath() ); + } + } + + FlushFileEntries( cluster, entry->entry.data.length, subDirPath ); } } } @@ -743,7 +788,7 @@ void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remaini // continue to the next cluster of this directory const u32 nextCluster = m_fat.data[0][0][dirCluster]; if ( nextCluster != 0xFFFFFFFF ) { - FlushFileEntries( nextCluster & 0x7FFFFFFF, remainingFiles - 2 ); + FlushFileEntries( nextCluster & 0x7FFFFFFF, remainingFiles - 2, dirPath ); } } @@ -824,9 +869,35 @@ bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { } file.Close(); - - return true; + } else { + return false; } + + // separately write metadata of file if it's nonstandard + fileName.AppendDir( L"_pcsx2_meta" ); + if ( entry->entry.data.mode != MemoryCardFileEntry::DefaultFileMode || entry->entry.data.attr != 0 ) { + if ( !fileName.DirExists() ) { + fileName.Mkdir(); + } + wxFFile metaFile( fileName.GetFullPath(), L"wb" ); + if ( metaFile.IsOpened() ) { + metaFile.Write( entry->entry.raw, 0x40 ); + metaFile.Close(); + } + } else { + // if metadata is standard remove metadata file if it exists + if ( fileName.FileExists() ) { + wxRemoveFile( fileName.GetFullPath() ); + + // and remove the metadata dir if it's now empty + wxDir metaDir( fileName.GetPath() ); + if ( metaDir.IsOpened() && !metaDir.HasFiles() ) { + wxRmdir( fileName.GetPath() ); + } + } + } + + return true; } return false; diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index dfdc769e4..2b53b60fb 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -94,6 +94,9 @@ struct MemoryCardFileEntry { bool IsDir() { return !!( entry.data.mode & 0x0020 ); } bool IsUsed() { return !!( entry.data.mode & 0x8000 ); } bool IsValid() { return entry.data.mode != 0xFFFFFFFF; } + + static const u32 DefaultDirMode = 0x8427; + static const u32 DefaultFileMode = 0x8497; }; #pragma pack(pop) @@ -269,7 +272,7 @@ protected: void Flush( const u32 page ); // flush a directory's file entries and all its subdirectories to the internal data - void FlushFileEntries( const u32 dirCluster, const u32 remainingFiles ); + void FlushFileEntries( const u32 dirCluster, const u32 remainingFiles, const wxString& dirPath = L"" ); // write data as Save() normally would, but ignore the cache; used for flushing s32 WriteWithoutCache( const u8 *src, u32 adr, int size ); From 880be6f6029ecffd0942d91ffadf7249a967c11c Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 9 May 2015 20:28:40 +0200 Subject: [PATCH 04/44] FolderMemoryCard: Only load data relevant to game. Please read commit description! Essentially, I'm telling the memory card to re-index itself with a filter based on the game's disc serial every time a new executable boots in the emulator. This currently works for a lot of games, but fails in edge cases where the game disc's serial does not match the game serial that is written to the memory card as part of the save file's directory name. This affects mostly (only?) games that have multiple discs. We could circumvent this by adding a "save game serial" or something into the GameDatabase which tells us what we should filter by for those cases. Apart from this edge case, this appears to work surprisingly well. Try it and see if you can find other issues! --- common/include/PluginCallbacks.h | 4 +++- pcsx2/PluginManager.cpp | 4 ++++ pcsx2/Plugins.h | 1 + pcsx2/Sio.cpp | 14 ++++++++++++++ pcsx2/Sio.h | 5 +++++ pcsx2/gui/AppCoreThread.cpp | 3 +++ pcsx2/gui/MemoryCardFile.cpp | 15 +++++++++++++++ pcsx2/gui/MemoryCardFolder.cpp | 24 ++++++++++++++++++++---- pcsx2/gui/MemoryCardFolder.h | 6 ++++-- 9 files changed, 69 insertions(+), 7 deletions(-) diff --git a/common/include/PluginCallbacks.h b/common/include/PluginCallbacks.h index ea2ff1859..7fdec1bf5 100644 --- a/common/include/PluginCallbacks.h +++ b/common/include/PluginCallbacks.h @@ -1109,7 +1109,9 @@ typedef struct _PS2E_ComponentAPI_Mcd // Used by the FolderMemoryCard to find a good time to flush written data to the host file system. void (PS2E_CALLBACK* McdNextFrame)( PS2E_THISPTR thisptr, uint port, uint slot ); - void* reserved[7]; + void (PS2E_CALLBACK* McdReIndex)( PS2E_THISPTR thisptr, uint port, uint slot, const wxString& filter ); + + void* reserved[6]; } PS2E_ComponentAPI_Mcd; diff --git a/pcsx2/PluginManager.cpp b/pcsx2/PluginManager.cpp index 46802ff86..3e8d28559 100644 --- a/pcsx2/PluginManager.cpp +++ b/pcsx2/PluginManager.cpp @@ -70,6 +70,10 @@ void SysPluginBindings::McdNextFrame( uint port, uint slot ) { Mcd->McdNextFrame( (PS2E_THISPTR) Mcd, port, slot ); } +void SysPluginBindings::McdReIndex( uint port, uint slot, const wxString& filter ) { + Mcd->McdReIndex( (PS2E_THISPTR) Mcd, port, slot, filter ); +} + // ---------------------------------------------------------------------------- // Yay, order of this array shouldn't be important. :) // diff --git a/pcsx2/Plugins.h b/pcsx2/Plugins.h index 5ed768c8a..368d48a8c 100644 --- a/pcsx2/Plugins.h +++ b/pcsx2/Plugins.h @@ -242,6 +242,7 @@ public: void McdEraseBlock( uint port, uint slot, u32 adr ); u64 McdGetCRC( uint port, uint slot ); void McdNextFrame( uint port, uint slot ); + void McdReIndex( uint port, uint slot, const wxString& filter ); friend class SysCorePlugins; }; diff --git a/pcsx2/Sio.cpp b/pcsx2/Sio.cpp index aa6eac688..54fe014b1 100644 --- a/pcsx2/Sio.cpp +++ b/pcsx2/Sio.cpp @@ -879,6 +879,20 @@ void sioNextFrame() { } } +// Used to figure out when a new game boots, so that memory cards can re-index themselves and only load data relevant to that game. +wxString SioCurrentGameSerial = L""; +void sioSetGameSerial( const wxString& serial ) { + if ( serial == SioCurrentGameSerial ) { return; } + SioCurrentGameSerial = serial; + + for ( uint port = 0; port < 2; ++port ) { + for ( uint slot = 0; slot < 4; ++slot ) { + mcds[port][slot].ReIndex( serial ); + } + } + SetForceMcdEjectTimeoutNow(); +} + void SaveStateBase::sioFreeze() { // CRCs for memory cards. diff --git a/pcsx2/Sio.h b/pcsx2/Sio.h index c57f7c049..192545fad 100644 --- a/pcsx2/Sio.h +++ b/pcsx2/Sio.h @@ -86,6 +86,10 @@ struct _mcd void NextFrame() { SysPlugins.McdNextFrame( port, slot ); } + + void ReIndex(const wxString& filter = L"") { + SysPlugins.McdReIndex( port, slot, filter ); + } }; struct _sio @@ -124,3 +128,4 @@ extern void sioInterrupt(); extern void InitializeSIO(u8 value); extern void SetForceMcdEjectTimeoutNow(); extern void sioNextFrame(); +extern void sioSetGameSerial(const wxString& serial); diff --git a/pcsx2/gui/AppCoreThread.cpp b/pcsx2/gui/AppCoreThread.cpp index b08a44749..464c04382 100644 --- a/pcsx2/gui/AppCoreThread.cpp +++ b/pcsx2/gui/AppCoreThread.cpp @@ -31,6 +31,7 @@ #include "Elfheader.h" #include "Patch.h" #include "R5900Exceptions.h" +#include "Sio.h" __aligned16 SysMtgsThread mtgsThread; __aligned16 AppCoreThread CoreThread; @@ -382,6 +383,8 @@ void AppCoreThread::ApplySettings( const Pcsx2Config& src ) } } + sioSetGameSerial( curGameKey ); + if (gameName.IsEmpty() && gameSerial.IsEmpty() && gameCRC.IsEmpty()) { // if all these conditions are met, it should mean that we're currently running BIOS code. diff --git a/pcsx2/gui/MemoryCardFile.cpp b/pcsx2/gui/MemoryCardFile.cpp index bd369c275..fc7d89cd1 100644 --- a/pcsx2/gui/MemoryCardFile.cpp +++ b/pcsx2/gui/MemoryCardFile.cpp @@ -552,6 +552,20 @@ static void PS2E_CALLBACK FileMcd_NextFrame( PS2E_THISPTR thisptr, uint port, ui } } +static void PS2E_CALLBACK FileMcd_ReIndex( PS2E_THISPTR thisptr, uint port, uint slot, const wxString& filter ) { + const uint combinedSlot = FileMcd_ConvertToSlot( port, slot ); + switch ( g_Conf->Mcd[combinedSlot].Type ) { + //case MemoryCardType::MemoryCard_File: + // thisptr->impl.ReIndex( combinedSlot, filter ); + // break; + case MemoryCardType::MemoryCard_Folder: + thisptr->implFolder.ReIndex( combinedSlot, filter ); + break; + default: + return; + } +} + Component_FileMcd::Component_FileMcd() { memzero( api ); @@ -567,6 +581,7 @@ Component_FileMcd::Component_FileMcd() api.McdEraseBlock = FileMcd_EraseBlock; api.McdGetCRC = FileMcd_GetCRC; api.McdNextFrame = FileMcd_NextFrame; + api.McdReIndex = FileMcd_ReIndex; } diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index aead262ee..668ad591c 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -47,6 +47,10 @@ bool FolderMemoryCard::IsFormatted() { } void FolderMemoryCard::Open() { + Open( L"" ); +} + +void FolderMemoryCard::Open( const wxString& filter ) { InitializeInternalData(); wxFileName configuredFileName( g_Conf->FullpathToMcd( m_slot ) ); @@ -80,7 +84,7 @@ void FolderMemoryCard::Open() { if ( disabled ) return; m_isEnabled = true; - LoadMemoryCardData(); + LoadMemoryCardData( filter ); SetTimeLastWrittenToNow(); m_framesUntilFlush = 0; @@ -98,7 +102,7 @@ void FolderMemoryCard::Close() { } } -void FolderMemoryCard::LoadMemoryCardData() { +void FolderMemoryCard::LoadMemoryCardData( const wxString& filter ) { bool formatted = false; // read superblock if it exists @@ -115,7 +119,7 @@ void FolderMemoryCard::LoadMemoryCardData() { CreateFat(); CreateRootDir(); MemoryCardFileEntry* const rootDirEntry = &m_fileEntryDict[m_superBlock.data.rootdir_cluster].entries[0]; - AddFolder( rootDirEntry, folderName.GetPath() ); + AddFolder( rootDirEntry, folderName.GetPath(), filter ); } } @@ -260,7 +264,7 @@ MemoryCardFileEntry* FolderMemoryCard::AppendFileEntryToDir( MemoryCardFileEntry return newFileEntry; } -bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath ) { +bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& filter ) { wxDir dir( dirPath ); if ( dir.IsOpened() ) { Console.WriteLn( L"(FolderMcd) Adding folder: %s", WX_STR( dirPath ) ); @@ -286,6 +290,14 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS ++entryNumber; } } else { + // if possible filter added directories by game serial + // this has the effective result of only files relevant to the current game being loaded into the memory card + // which means every game essentially sees the memory card as if no other files exist + if ( !filter.IsEmpty() && !fileName.Contains( filter ) ) { + hasNext = dir.GetNext( &fileName ); + continue; + } + // make sure we have enough space on the memcard for the directory const u32 newNeededClusters = ( dirEntry->entry.data.length % 2 ) == 0 ? 2 : 1; if ( newNeededClusters > GetAmountFreeDataClusters() ) { @@ -1029,4 +1041,8 @@ void FolderMemoryCardAggregator::NextFrame( uint slot ) { m_cards[slot].NextFrame(); } +void FolderMemoryCardAggregator::ReIndex( uint slot, const wxString& filter ) { + m_cards[slot].Close(); + m_cards[slot].Open( filter ); +} diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 2b53b60fb..8df9aff6c 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -169,6 +169,7 @@ public: void Unlock(); void Open(); + void Open( const wxString& filter ); void Close(); s32 IsPresent(); @@ -219,7 +220,7 @@ protected: // loads files and folders from the host file system if a superblock exists in the root directory - void LoadMemoryCardData(); + void LoadMemoryCardData( const wxString& filter ); // creates the FAT and indirect FAT void CreateFat(); @@ -252,7 +253,7 @@ protected: // adds a folder in the host file system to the memory card, including all files and subdirectories // - dirEntry: the entry of the directory in the parent directory, or the root "." entry // - dirPath: the full path to the directory in the host file system - bool AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath ); + bool AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& filter = L"" ); // adds a file in the host file sytem to the memory card // - dirEntry: the entry of the directory in the parent directory, or the root "." entry @@ -313,4 +314,5 @@ public: s32 EraseBlock( uint slot, u32 adr ); u64 GetCRC( uint slot ); void NextFrame( uint slot ); + void ReIndex( uint slot, const wxString& filter ); }; From 2ee1cb81dd226ad705faead08e1ca5064cb70ed6 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 22 May 2015 19:30:41 +0200 Subject: [PATCH 05/44] FolderMemoryCard: Allow multiple save names in the save file filtering. Split multiple save names to be filtered with a "/", ie the filter "A/B" matches both save folders that contain "A" and save folders that contain "B". --- pcsx2/gui/MemoryCardFolder.cpp | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 668ad591c..ec7cc49ee 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -264,6 +264,26 @@ MemoryCardFileEntry* FolderMemoryCard::AppendFileEntryToDir( MemoryCardFileEntry return newFileEntry; } +bool FilterMatches( const wxString& fileName, const wxString& filter ) { + size_t start = 0; + size_t len = filter.Len(); + while ( start < len ) { + size_t end = filter.find( '/', start ); + if ( end == wxString::npos ) { + end = len; + } + + wxString singleFilter = filter.Mid( start, end - start ); + if ( fileName.Contains( singleFilter ) ) { + return true; + } + + start = end + 1; + } + + return false; +} + bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& filter ) { wxDir dir( dirPath ); if ( dir.IsOpened() ) { @@ -274,6 +294,12 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS wxString fileName; bool hasNext; + wxString localFilter; + bool hasFilter = !filter.IsEmpty(); + if ( hasFilter ) { + localFilter = L"DATA-SYSTEM/" + filter; + } + int entryNumber = 2; // include . and .. hasNext = dir.GetFirst( &fileName ); while ( hasNext ) { @@ -293,7 +319,7 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS // if possible filter added directories by game serial // this has the effective result of only files relevant to the current game being loaded into the memory card // which means every game essentially sees the memory card as if no other files exist - if ( !filter.IsEmpty() && !fileName.Contains( filter ) ) { + if ( hasFilter && !FilterMatches( fileName, localFilter ) ) { hasNext = dir.GetNext( &fileName ); continue; } @@ -1043,6 +1069,7 @@ void FolderMemoryCardAggregator::NextFrame( uint slot ) { void FolderMemoryCardAggregator::ReIndex( uint slot, const wxString& filter ) { m_cards[slot].Close(); + Console.WriteLn( Color_Green, L"(FolderMcd) Re-Indexing slot %u with filter \"%s\"", slot, WX_STR( filter ) ); m_cards[slot].Open( filter ); } From 98012f82aa52a5d0042fc2cdd40d9051345bbd69 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 23 May 2015 04:42:40 +0200 Subject: [PATCH 06/44] MemoryCard: Add support to override the memory card filter string via GameIndex.dbf. --- pcsx2/gui/AppCoreThread.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pcsx2/gui/AppCoreThread.cpp b/pcsx2/gui/AppCoreThread.cpp index 464c04382..f7b7a8688 100644 --- a/pcsx2/gui/AppCoreThread.cpp +++ b/pcsx2/gui/AppCoreThread.cpp @@ -345,6 +345,7 @@ void AppCoreThread::ApplySettings( const Pcsx2Config& src ) wxString gameName; wxString gameCompat; + wxString gameMemCardFilter; int numberLoadedCheats; int numberLoadedWideScreenPatches; @@ -369,6 +370,7 @@ void AppCoreThread::ApplySettings( const Pcsx2Config& src ) gameName = game.getString("Name"); gameName += L" (" + game.getString("Region") + L")"; gameCompat = L" [Status = "+compatToStringWX(compat)+L"]"; + gameMemCardFilter = game.getString("MemCardFilter"); } if (EmuConfig.EnablePatches) { @@ -383,7 +385,11 @@ void AppCoreThread::ApplySettings( const Pcsx2Config& src ) } } - sioSetGameSerial( curGameKey ); + if (!gameMemCardFilter.IsEmpty()) { + sioSetGameSerial(gameMemCardFilter); + } else { + sioSetGameSerial(curGameKey); + } if (gameName.IsEmpty() && gameSerial.IsEmpty() && gameCRC.IsEmpty()) { From bcb71ae368e1ee98d2f4faa7bf28a5907f742846 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 25 May 2015 03:40:39 +0200 Subject: [PATCH 07/44] GameIndex.dbf: Fixed a few names, added a few missing games and multi-discs, added a few missing regions. --- bin/GameIndex.dbf | 143 ++++++++++++++++++++++++++++++---------------- 1 file changed, 93 insertions(+), 50 deletions(-) diff --git a/bin/GameIndex.dbf b/bin/GameIndex.dbf index fc61b6675..858a3c62c 100644 --- a/bin/GameIndex.dbf +++ b/bin/GameIndex.dbf @@ -207,7 +207,7 @@ Name = Taiko No Tatsujin Doka! To Omori 7Daime Region = NTSC-Unk --------------------------------------------- Serial = SCAJ-20001 -Name = Ratchet and Clank +Name = Ratchet & Clank Region = NTSC-Unk --------------------------------------------- Serial = SCAJ-20002 @@ -2726,7 +2726,7 @@ Name = Jak II Region = NTSC-K --------------------------------------------- Serial = SCKA-20011 -Name = Ratchet and Clank 2 +Name = Ratchet & Clank 2 Region = NTSC-K --------------------------------------------- Serial = SCKA-20012 @@ -2816,6 +2816,10 @@ Serial = SCKA-20035 Name = Hot Shots Golf 3 [PlayStation 2 Big Hit Series] Region = NTSC-K --------------------------------------------- +Serial = SCKA-20037 +Name = Ratchet & Clank 3 +Region = NTSC-K +--------------------------------------------- Serial = SCKA-20038 Name = Time Crisis - Crisis Zone Region = NTSC-K @@ -3001,7 +3005,7 @@ Name = Super Robot Taisen OG - Original Generations Gaiden Region = NTSC-K --------------------------------------------- Serial = SCKA-20120 -Name = Ratchet and Clank +Name = Ratchet & Clank Region = NTSC-K --------------------------------------------- Serial = SCKA-20132 @@ -3618,7 +3622,7 @@ Name = Formula One 2005 Region = NTSC-J --------------------------------------------- Serial = SCPS-15099 -Name = Ratchet & Clank - Giga Battle +Name = Ratchet & Clank 4th - GiriGiri Ginga no Giga Battle Region = NTSC-J --------------------------------------------- Serial = SCPS-15100 @@ -3935,7 +3939,7 @@ Name = Wanda to Kyozou (Shadow of the Colossus) [PlayStation 2 The Best] Region = NTSC-J --------------------------------------------- Serial = SCPS-19321 -Name = Ratchet & Clank 4th - GiriGiri Gingano Giga-Battle [PlayStation 2 The Best] +Name = Ratchet & Clank 4th - GiriGiri Ginga no Giga Battle [PlayStation 2 The Best] Region = NTSC-J --------------------------------------------- Serial = SCPS-19322 @@ -3964,7 +3968,7 @@ Name = Ape Escape 3 [PlayStation 2 the Best - Reprint] Region = NTSC-J --------------------------------------------- Serial = SCPS-19328 -Name = Ratchet & Clank 4th Girigiri Gingano Giga-battle [PlayStation 2 the Best - Reprint] +Name = Ratchet & Clank 4th - GiriGiri Ginga no Giga Battle [PlayStation 2 the Best - Reprint] Region = NTSC-J --------------------------------------------- Serial = SCPS-19329 @@ -5154,7 +5158,7 @@ Name = NBA '06 Region = NTSC-U --------------------------------------------- Serial = SCUS-97353 -Name = Ratchet and Clank - Up Your Arsenal +Name = Ratchet & Clank - Up Your Arsenal Region = NTSC-U Compat = 5 --------------------------------------------- @@ -5705,7 +5709,7 @@ Name = Killzone [Greatest Hits] Region = NTSC-U --------------------------------------------- Serial = SCUS-97518 -Name = Ratchet and Clank - Up Your Arsenal [Greatest Hits] +Name = Ratchet & Clank - Up Your Arsenal [Greatest Hits] Region = NTSC-U --------------------------------------------- Serial = SCUS-97519 @@ -9890,7 +9894,7 @@ Region = PAL-Unk --------------------------------------------- Serial = SLES-51693 Name = Suffering, The -Region = PAL-E +Region = PAL-E-F-G --------------------------------------------- Serial = SLES-51696 Name = Dragon's Lair 3D - Special Edition @@ -11408,7 +11412,7 @@ Region = PAL-Unk --------------------------------------------- Serial = SLES-52439 Name = Suffering, The -Region = PAL-E +Region = PAL-E-I-S Compat = 5 --------------------------------------------- Serial = SLES-52440 @@ -11625,7 +11629,7 @@ Region = PAL-Unk --------------------------------------------- Serial = SLES-52531 Name = Suffering, The -Region = PAL-Unk +Region = PAL-G --------------------------------------------- Serial = SLES-52532 Name = Aces of War @@ -11681,15 +11685,23 @@ Region = PAL-Unk --------------------------------------------- Serial = SLES-52551 Name = Samurai Warriors -Region = PAL-Unk +Region = PAL-E +--------------------------------------------- +Serial = SLES-52552 +Name = Samurai Warriors +Region = PAL-F --------------------------------------------- Serial = SLES-52553 Name = Samurai Warriors -Region = PAL-Unk +Region = PAL-G +--------------------------------------------- +Serial = SLES-52554 +Name = Samurai Warriors +Region = PAL-I --------------------------------------------- Serial = SLES-52555 Name = Samurai Warriors -Region = PAL-Unk +Region = PAL-S --------------------------------------------- Serial = SLES-52556 Name = Crimson Sea 2 @@ -13919,9 +13931,13 @@ Serial = SLES-53525 Name = Mortal Kombat - Shaolin Monks Region = PAL-G --------------------------------------------- +Serial = SLES-53526 +Name = Suffering, The - Ties that Bind +Region = PAL-E-F +--------------------------------------------- Serial = SLES-53527 Name = Suffering, The - Ties that Bind -Region = PAL-Unk +Region = PAL-E-I-S --------------------------------------------- Serial = SLES-53528 Name = Suffering, The - Ties that Bind @@ -16164,7 +16180,7 @@ Region = PAL-E Compat = 5 --------------------------------------------- Serial = SLES-54629 -Name = Shin Megami Tensei - Devil Summoner +Name = Shin Megami Tensei - Devil Summoner - Raidou Kuzunoha vs. the Soulless Army Region = PAL-E --------------------------------------------- Serial = SLES-54631 @@ -17418,8 +17434,12 @@ Region = PAL-E-F Compat = 5 --------------------------------------------- Serial = SLES-82018 -Name = Cy Girls -Region = PAL-Unk +Name = Cy Girls [Disc 1] +Region = PAL-E-F-S +--------------------------------------------- +Serial = SLES-82019 +Name = Cy Girls [Disc 2] +Region = PAL-E-F-S --------------------------------------------- Serial = SLES-82020 Name = Cy Girls [Disc 1] @@ -17496,51 +17516,51 @@ Compat = 5 [/patches] --------------------------------------------- Serial = SLES-82042 -Name = Metal Gear Solid 3 - Subsistance [Disc1of2] +Name = Metal Gear Solid 3 - Subsistence [Disc1of3] Region = PAL-Unk --------------------------------------------- Serial = SLES-82043 -Name = Metal Gear Solid 3 - Subsistance [Disc2of2] +Name = Metal Gear Solid 3 - Subsistence [Disc2of3] Region = PAL-Unk --------------------------------------------- Serial = SLES-82044 -Name = Metal Gear Solid 3 - Subsistance [Disc1of2] +Name = Metal Gear Solid 3 - Subsistence [Disc1of3] Region = PAL-Unk --------------------------------------------- Serial = SLES-82045 -Name = Metal Gear Solid 3 - Subsistance [Disc2of2] +Name = Metal Gear Solid 3 - Subsistence [Disc2of3] Region = PAL-Unk --------------------------------------------- Serial = SLES-82046 -Name = Metal Gear Solid 3 - Subsistance [Disc1of2] +Name = Metal Gear Solid 3 - Subsistence [Disc1of3] Region = PAL-Unk --------------------------------------------- Serial = SLES-82047 -Name = Metal Gear Solid 3 - Subsistance [Disc2of2] +Name = Metal Gear Solid 3 - Subsistence [Disc2of3] Region = PAL-Unk --------------------------------------------- Serial = SLES-82048 -Name = Metal Gear Solid 3 - Subsistance [Disc1of2] +Name = Metal Gear Solid 3 - Subsistence [Disc1of3] Region = PAL-Unk --------------------------------------------- Serial = SLES-82049 -Name = Metal Gear Solid 3 - Subsistance [Disc2of2] +Name = Metal Gear Solid 3 - Subsistence [Disc2of3] Region = PAL-Unk --------------------------------------------- Serial = SLES-82050 -Name = Metal Gear Solid 3 - Subsistance [Disc1of2] +Name = Metal Gear Solid 3 - Subsistence [Disc3of3] Region = PAL-Unk --------------------------------------------- Serial = SLES-82051 -Name = Metal Gear Solid 3 - Subsistance [Disc2of2] +Name = Metal Gear Solid 3 - Subsistence [Disc3of3] Region = PAL-Unk --------------------------------------------- Serial = SLES-82052 -Name = Metal Gear Solid 3 - Subsistance [Disc1of2] +Name = Metal Gear Solid 3 - Subsistence [Disc3of3] Region = PAL-Unk --------------------------------------------- Serial = SLES-82053 -Name = Metal Gear Solid 3 - Subsistance [Disc2of2] +Name = Metal Gear Solid 3 - Subsistence [Disc3of3] Region = PAL-Unk --------------------------------------------- Serial = SLKA-15003 @@ -17697,7 +17717,7 @@ Name = SD Gundam G Generation Neo Region = NTSC-K --------------------------------------------- Serial = SLKA-25082 -Name = Castlevania Lament of Innocence +Name = Castlevania - Lament of Innocence Region = NTSC-K Compat = 5 --------------------------------------------- @@ -18120,7 +18140,7 @@ Name = SSX On Tour Region = NTSC-J-K --------------------------------------------- Serial = SLKA-25328 -Name = Castlevania - Curse of Dakness +Name = Castlevania - Curse of Darkness Region = NTSC-K Compat = 5 vuClampMode = 0 //SPS with microVU @@ -18154,12 +18174,12 @@ Name = Full Metal Alchemist - Dream Carnival Region = NTSC-K --------------------------------------------- Serial = SLKA-25353 -Name = Metal Gear Solid 3 - Subsistance [Limited Edition] [Disc1of2] +Name = Metal Gear Solid 3 - Subsistence [Limited Edition] [Disc1of2] Region = NTSC-K Compat = 5 --------------------------------------------- Serial = SLKA-25354 -Name = Metal Gear Solid 3 - Subsistance [Limited Edition] [Disc2of2] +Name = Metal Gear Solid 3 - Subsistence [Limited Edition] [Disc2of2] Region = NTSC-K Compat = 5 --------------------------------------------- @@ -23251,7 +23271,7 @@ Name = Ever 17 - Out of Infinity [SuperLite 2000 Series] Region = NTSC-J --------------------------------------------- Serial = SLPM-65692 -Name = BioHazard Outbreak - File 2 +Name = BioHazard Outbreak - File #2 Region = NTSC-J Compat = 5 --------------------------------------------- @@ -24318,7 +24338,12 @@ Name = World Rally Championship 4 Region = NTSC-J --------------------------------------------- Serial = SLPM-65976 -Name = Grandia III +Name = Grandia III [Disc1of2] +Region = NTSC-J +Compat = 5 +--------------------------------------------- +Serial = SLPM-65977 +Name = Grandia III [Disc2of2] Region = NTSC-J Compat = 5 --------------------------------------------- @@ -25258,7 +25283,7 @@ Name = Jewels Ocean - Star of Sierra Leone Region = NTSC-J --------------------------------------------- Serial = SLPM-66246 -Name = Shin Megami Tensei - Devil Summoner - Kuzunoha Raidou +Name = Devil Summoner - Kuzunoha Raidou tai Chouriki Heidan Region = NTSC-J Compat = 5 --------------------------------------------- @@ -26535,7 +26560,11 @@ Name = SSX On Tour [EA Best Hits] Region = NTSC-J --------------------------------------------- Serial = SLPM-66602 -Name = Ryu ga Gotoku 2 +Name = Ryu ga Gotoku 2 [Disc1of2] +Region = NTSC-J +--------------------------------------------- +Serial = SLPM-66603 +Name = Ryu ga Gotoku 2 [Disc2of2] Region = NTSC-J --------------------------------------------- Serial = SLPM-66604 @@ -26652,7 +26681,7 @@ Name = Shoujo Mahou Gaku Little Witch Romanesque Region = NTSC-J --------------------------------------------- Serial = SLPM-66634 -Name = Devil Summoner - Kuzunoha Raidou [Atlus Best Collection] +Name = Devil Summoner - Kuzunoha Raidou tai Chouriki Heidan [Atlus Best Collection] Region = NTSC-J --------------------------------------------- Serial = SLPM-66635 @@ -29978,7 +30007,11 @@ Name = Tales of the Sunrise Heroes 2 Region = NTSC-J --------------------------------------------- Serial = SLPS-25071 -Name = Visual Mix - Ayumi Hamasaki Dome Tour 2001 +Name = Visual Mix - Ayumi Hamasaki Dome Tour 2001 [Disc1of2] +Region = NTSC-J +--------------------------------------------- +Serial = SLPS-25072 +Name = Visual Mix - Ayumi Hamasaki Dome Tour 2001 [Disc2of2] Region = NTSC-J --------------------------------------------- Serial = SLPS-25074 @@ -30762,7 +30795,11 @@ Region = NTSC-J Compat = 5 --------------------------------------------- Serial = SLPS-25317 -Name = Shadow Hearts 2 [Deluxe Pack] +Name = Shadow Hearts 2 [Deluxe Pack] [Disc1of2] +Region = NTSC-J +--------------------------------------------- +Serial = SLPS-25318 +Name = Shadow Hearts 2 [Deluxe Pack] [Disc2of2] Region = NTSC-J --------------------------------------------- Serial = SLPS-25319 @@ -30807,7 +30844,11 @@ Name = Gallop Racer Lucky 7 Region = NTSC-J --------------------------------------------- Serial = SLPS-25334 -Name = Shadow Hearts 2 +Name = Shadow Hearts 2 [Disc1of2] +Region = NTSC-J +--------------------------------------------- +Serial = SLPS-25335 +Name = Shadow Hearts 2 [Disc1of2] Region = NTSC-J --------------------------------------------- Serial = SLPS-25336 @@ -31317,9 +31358,11 @@ Name = Eternal Aselia - The Spirit of Eternity Sword [Limited Edition] Region = NTSC-J --------------------------------------------- Serial = SLPS-25467 -Name = Minna Daisuki Katamaki Damacy +Name = Minna Daisuki Katamari Damacy Region = NTSC-J Compat = 5 +vuClampMode = 3 +mvuFlagSpeedHack = 0 --------------------------------------------- Serial = SLPS-25468 Name = Eternal Aselia - The Spirit of Eternity Sword @@ -37670,7 +37713,7 @@ Region = NTSC-U Compat = 5 --------------------------------------------- Serial = SLUS-20984 -Name = Resident Evil - Outbreak File #2 +Name = Resident Evil Outbreak - File #2 Region = NTSC-U Compat = 5 --------------------------------------------- @@ -37963,7 +38006,7 @@ Region = NTSC-U Compat = 5 --------------------------------------------- Serial = SLUS-21041 -Name = Shadow Hearts 2 - Covenant [Disc1of2] +Name = Shadow Hearts - Covenant [Disc1of2] Region = NTSC-U Compat = 5 --------------------------------------------- @@ -37978,7 +38021,7 @@ Region = NTSC-U Compat = 5 --------------------------------------------- Serial = SLUS-21044 -Name = Shadow Hearts 2 - Covenant [Disc2of2] +Name = Shadow Hearts - Covenant [Disc2of2] Region = NTSC-U Compat = 5 --------------------------------------------- @@ -38543,7 +38586,7 @@ Region = NTSC-U Compat = 5 --------------------------------------------- Serial = SLUS-21168 -Name = Castlevania - Curse of Dakness +Name = Castlevania - Curse of Darkness Region = NTSC-U Compat = 5 vuClampMode = 0 //SPS with microVU @@ -38908,7 +38951,7 @@ Region = NTSC-U Compat = 5 --------------------------------------------- Serial = SLUS-21243 -Name = Metal Gear Solid 3 - Subsistence +Name = Metal Gear Solid 3 - Subsistence [Disc2of3] Region = NTSC-U Compat = 5 --------------------------------------------- @@ -39468,12 +39511,12 @@ Region = NTSC-U Compat = 5 --------------------------------------------- Serial = SLUS-21359 -Name = Metal Gear Solid 3 - Subsistence [Limited Edition] [Disc2of3] +Name = Metal Gear Solid 3 - Subsistence [Disc1of3] Region = NTSC-U Compat = 5 --------------------------------------------- Serial = SLUS-21360 -Name = Metal Gear Solid 3 - Subsistence [Limited Edition] [Disc3of3] +Name = Metal Gear Solid 3 - Subsistence [Disc3of3] Region = NTSC-U --------------------------------------------- Serial = SLUS-21361 From 02ae12c555f7c9bffaac65c605dd200a12d95f38 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 25 May 2015 03:57:50 +0200 Subject: [PATCH 08/44] GameIndex.dbf: Add a the initial set of Memory Card Filters. This informs PCSX2 which games want to access more than just their own save files so it can load them into the virtual folder memory card. This includes: - Multi-disc games which obviously need to access saves from the other disc(s). - Games that allow importing data from prequels. - Games that unlock bonuses if they find data from other games in the series, by the same developer, etc. This is almost certainly not all games that would want to access other saves, but it should cover a lot them. --- bin/GameIndex.dbf | 293 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) diff --git a/bin/GameIndex.dbf b/bin/GameIndex.dbf index 858a3c62c..551b931ca 100644 --- a/bin/GameIndex.dbf +++ b/bin/GameIndex.dbf @@ -74,6 +74,19 @@ --------------------------------------------- -- mvuFlagSpeedHack = 1 or 0 // Katamari Damacy have weird speed bug when this speed hack is enabled (and it is by default) +--------------------------------------------- +-- Memory Card Filter Override (MemCardFilter = s) +--------------------------------------------- +-- By default, the FolderMemoryCard filters save games based on the +-- game's serial, which means that only saves whose folder names contain +-- the game's serial are loaded. This works fine for the vast majority +-- of games, but fails in some cases, for which this override is for. +-- Examples include multi-disc games, where later games often reuse the +-- serial of the previous disc(s), and games that allow transfer of save +-- data between different games, such as importing data from a prequel. +-- To allow multiple serials separate them with slashes, like this: +-- MemCardFilter = SLUS-12345/SLUS-12346/SLUS-12347 + --------------------------------------------- -- Patches ([patches] or [patches = crc]) --------------------------------------------- @@ -221,6 +234,7 @@ Region = NTSC-Unk Serial = SCAJ-20004 Name = dot Hack Vol.3 Region = NTSC-Unk +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233 --------------------------------------------- Serial = SCAJ-20005 Name = Guilty Gear XX @@ -301,6 +315,7 @@ Region = NTSC-Unk Serial = SCAJ-20024 Name = dot Hack Vol.4 Region = NTSC-Unk +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233 --------------------------------------------- Serial = SCAJ-20025 Name = Grand Prix Challenge @@ -440,6 +455,7 @@ Region = NTSC-Unk Compat = 5 //eeClampMode = 3 // Text in races works vuClampMode = 2 // Text in GT mode works +MemCardFilter = SCAJ-20066/SCAJ-30006/SCAJ-30007/SCAJ-30008/SCPS-15055/SCPS-17001/SCPS-19252/SCPS-19304/SCPS-15009/SCPS-55007 --------------------------------------------- Serial = SCAJ-20067 Name = GunGrave O.D. @@ -514,10 +530,12 @@ Region = NTSC-Unk Serial = SCAJ-20086 Name = Xenosaga Episode II - Jenseits von Gut und Bose [Disc1of2] Region = NTSC-Unk +MemCardFilter = SLPS-29001/SLPS-29002/SLPS-29005/SLPS-25368/SLPS-25353/SLPS-73224 --------------------------------------------- Serial = SCAJ-20087 Name = Xenosaga Episode II - Jenseits von Gut und Bose [Disc2of2] Region = NTSC-Unk +MemCardFilter = SLPS-29001/SLPS-29002/SLPS-29005/SLPS-25368/SLPS-25353/SLPS-73224 --------------------------------------------- Serial = SCAJ-20088 Name = UO Nanatsu no Mizu to Densetsu no Nushi @@ -644,6 +662,7 @@ Serial = SCAJ-20120 Name = Digital Devil Saga: Avatar Tuner 2 Region = NTSC-Unk EETimingHack = 1 +MemCardFilter = SCAJ-20120/SLPM-65795/SLPM-66373/SCAJ-20095/SLPM-65597/SLPM-66372 --------------------------------------------- Serial = SCAJ-20121 Name = Armored Core - Formula Front @@ -656,6 +675,7 @@ Region = NTSC-Unk Serial = SCAJ-20123 Name = Wild ARMs - The 4th Detonator Region = NTSC-Unk +MemCardFilter = SCAJ-20123/SCPS-15091/SCPS-15092/SCPS-19313/SCPS-19322/SCPS-19323/SCAJ-30002/SCPS-17002/SCPS-19251/SCPS-19253 --------------------------------------------- Serial = SCAJ-20124 Name = Romancing Saga - Minstrel Song @@ -708,6 +728,7 @@ Name = Minna Daisuki Katamari Damacy Region = NTSC-Unk vuClampMode = 3 mvuFlagSpeedHack = 0 +MemCardFilter = SCAJ-20135/SLPS-25467/SLPS-73241/SCAJ-20079/SLPS-25360/SLPS-73210/SLPS-73240 --------------------------------------------- Serial = SCAJ-20136 Name = Ace Combat 5 - The Unsung War [PlayStation 2 The Best] @@ -749,6 +770,7 @@ Serial = SCAJ-20146 Name = Shadow of the Colossus Region = NTSC-Ch-E-J Compat = 5 +MemCardFilter = SCAJ-20146/SCAJ-20196/SCAJ-20099/SCPS-11003/SCPS-19103/SCPS-19151/SCPS-55001 --------------------------------------------- Serial = SCAJ-20147 Name = Heavy Metal Thunder @@ -860,6 +882,7 @@ Region = NTSC-Unk Serial = SCAJ-20173 Name = Ace Combat Zero - The Belkan War Region = NTSC-Unk +MemCardFilter = SCAJ-20173/SLPS-25629/SLPS-73250/SLPS-25052/SLPS-73205/SLPS-73410/SCAJ-20104/SCAJ-20136/SLPS-25418/SLPS-73218 --------------------------------------------- Serial = SCAJ-20175 Name = Dragon Quest - Shonen Yangus to Fushigi no Dungeon @@ -941,6 +964,7 @@ Region = NTSC-Ch Serial = SCAJ-20196 Name = Shadow of the Colossus [PlayStation 2 The Best] Region = NTSC-Ch +MemCardFilter = SCAJ-20146/SCAJ-20196/SCAJ-20099/SCPS-11003/SCPS-19103/SCPS-19151/SCPS-55001 --------------------------------------------- Serial = SCAJ-20197 Name = Valkyrie Profile 2 - Silmeria [Ultimate Hits] @@ -1019,6 +1043,7 @@ Name = Gran Turismo 4 Region = NTSC-Unk //eeClampMode = 3 // Text in races works vuClampMode = 2 // Text in GT mode works +MemCardFilter = SCAJ-20066/SCAJ-30006/SCAJ-30007/SCAJ-30008/SCPS-15055/SCPS-17001/SCPS-19252/SCPS-19304/SCPS-15009/SCPS-55007 --------------------------------------------- Serial = SCAJ-30007 Name = Gran Turismo 4 @@ -1026,6 +1051,7 @@ Region = NTSC-Unk Compat = 5 //eeClampMode = 3 // Text in races works vuClampMode = 2 // Text in GT mode works +MemCardFilter = SCAJ-20066/SCAJ-30006/SCAJ-30007/SCAJ-30008/SCPS-15055/SCPS-17001/SCPS-19252/SCPS-19304/SCPS-15009/SCPS-55007 //[patches = 7ABDBB5E] // // comment=patches by nachbrenner @@ -1040,6 +1066,7 @@ Name = Gran Turismo 4 [PlayStation 2 The Best] Region = NTSC-Unk //eeClampMode = 3 // Text in races works vuClampMode = 2 // Text in GT mode works +MemCardFilter = SCAJ-20066/SCAJ-30006/SCAJ-30007/SCAJ-30008/SCPS-15055/SCPS-17001/SCPS-19252/SCPS-19304/SCPS-15009/SCPS-55007 --------------------------------------------- Serial = SCAJ-30010 Name = God of War @@ -1636,6 +1663,8 @@ Serial = SCES-51607 Name = Ratchet & Clank 2 - Locked & Loaded Region = PAL-Unk Compat = 5 +// reads Ratchet 1 data +MemCardFilter = SCES-51607/SCES-50916 --------------------------------------------- Serial = SCES-51608 Name = Jak & Daxter 2 - Renegade @@ -1702,6 +1731,7 @@ Region = PAL-Unk Compat = 5 //eeClampMode = 3 // Text in races works vuClampMode = 2 // Text in GT mode works +MemCardFilter = SCES-51719/SCES-52438/SCES-50294 --------------------------------------------- Serial = SCES-51725 Name = Everquest Online Adventures @@ -1893,11 +1923,14 @@ Region = PAL-Unk Compat = 5 //eeClampMode = 3 // Text in races works vuClampMode = 2 // Text in GT mode works +MemCardFilter = SCES-51719/SCES-52438/SCES-50294 --------------------------------------------- Serial = SCES-52456 Name = Ratchet & Clank 3 Region = PAL-Unk Compat = 5 +// reads Ratchet 1 & 2 data +MemCardFilter = SCES-52456/SCES-51607/SCES-50916 --------------------------------------------- Serial = SCES-52460 Name = Jak 3 @@ -2107,6 +2140,8 @@ Compat = 5 Serial = SCES-53286 Name = Jak X - Combat Racing Region = PAL-Unk +// reads Ratchet Gladiator data +MemCardFilter = SCES-53286/SCES-53285 --------------------------------------------- Serial = SCES-53300 Name = SOCOM 3 - U.S. Navy SEALs @@ -2151,6 +2186,7 @@ Serial = SCES-53326 Name = Shadow of the Colossus Region = PAL-Unk Compat = 5 +MemCardFilter = SCES-53326/SCES-50760 --------------------------------------------- Serial = SCES-53328 Name = Genji @@ -2354,6 +2390,8 @@ Region = PAL-Unk Serial = SCES-54041 Name = Ace Combat - The Belkan War Region = PAL-E +// reads AC4 and 5 saves for bonus unlockables +MemCardFilter = SCES-54041/SCES-50410/SCES-52424 [patches = 194C9F38] patch=0,EE,00131EB4,word,48498800 patch=0,EE,00131EB8,word,4B00682C @@ -2696,10 +2734,12 @@ Compat = 5 Serial = SCES-82034 Name = Xenosaga II - Jenseits von Gut und Bose [Disc 1] Region = PAL-Unk +MemCardFilter = SLES-82034/SCES-82034 --------------------------------------------- Serial = SCES-82035 Name = Xenosaga II - Jenseits von Gut und Bose [Disc 2] Region = PAL-Unk +MemCardFilter = SLES-82034/SCES-82034 --------------------------------------------- Serial = SCKA-10006 Name = Come on Baby @@ -2728,6 +2768,7 @@ Region = NTSC-K Serial = SCKA-20011 Name = Ratchet & Clank 2 Region = NTSC-K +MemCardFilter = SCKA-20011/SCKA-20120 --------------------------------------------- Serial = SCKA-20012 Name = Ark the Lad - jeongryeongui Hwanghon @@ -2819,6 +2860,7 @@ Region = NTSC-K Serial = SCKA-20037 Name = Ratchet & Clank 3 Region = NTSC-K +MemCardFilter = SCKA-20037/SCKA-20011/SCKA-20120 --------------------------------------------- Serial = SCKA-20038 Name = Time Crisis - Crisis Zone @@ -2864,6 +2906,7 @@ Name = Minna Daisuki Katamari Damacy Region = NTSC-K vuClampMode = 3 mvuFlagSpeedHack = 0 +MemCardFilter = SCKA-20051/SCKA-20025 --------------------------------------------- Serial = SCKA-20052 Name = Genji @@ -2906,6 +2949,7 @@ Serial = SCKA-20061 Name = Wanda to Kyozou (Shadow of the Colossus) Region = NTSC-K Compat = 5 +MemCardFilter = SCKA-20061/SCPS-15097/SCPS-19320/SCKA-20028/SCPS-56001 --------------------------------------------- Serial = SCKA-20062 Name = Ape Escape 3 @@ -2963,6 +3007,7 @@ Region = NTSC-K Serial = SCKA-20087 Name = Shin Onimusha - Dawn of Dreams [Disc2of2] Region = NTSC-K +MemCardFilter = SCKA-20086 --------------------------------------------- Serial = SCKA-20090 Name = God Hand @@ -2995,6 +3040,7 @@ Serial = SCKA-20109 Name = Persona 3 FES [Independent Starting Version] Region = NTSC-K VuClipFlagHack = 1 +MemCardFilter = SCKA-20109/SCKA-20099 --------------------------------------------- Serial = SCKA-20114 Name = Obscure II - The Aftermath @@ -3129,10 +3175,12 @@ Region = NTSC-J Serial = SCPS-11019 Name = Bravo Music - Chou-Meikyokuban [Limited Edition] [Disc1of2] Region = NTSC-J +MemCardFilter = SCPS-11023/SCPS-11019/SCPS-11020 --------------------------------------------- Serial = SCPS-11020 Name = Bravo Music - Chou-Meikyokuban [Limited Edition] [Disc2of2] Region = NTSC-J +MemCardFilter = SCPS-11023/SCPS-11019/SCPS-11020 --------------------------------------------- Serial = SCPS-11021 Name = Yoake no Mariko 2nd Act @@ -3145,6 +3193,7 @@ Region = NTSC-J Serial = SCPS-11023 Name = Bravo Music - Chou-Meikyokuban Region = NTSC-J +MemCardFilter = SCPS-11023/SCPS-11019/SCPS-11020 --------------------------------------------- Serial = SCPS-11024 Name = Otostaz @@ -3439,10 +3488,12 @@ Name = Gran Turismo 4 - Prologue Region = NTSC-J //eeClampMode = 3 // Text in races works vuClampMode = 2 // Text in GT mode works +MemCardFilter = SCAJ-20066/SCAJ-30006/SCAJ-30007/SCAJ-30008/SCPS-15055/SCPS-17001/SCPS-19252/SCPS-19304/SCPS-15009/SCPS-55007 --------------------------------------------- Serial = SCPS-15056 Name = Ratchet & Clank 2 Region = NTSC-J +MemCardFilter = SCPS-15056/SCPS-15037 --------------------------------------------- Serial = SCPS-15057 Name = Jak & Daxter 2 @@ -3560,6 +3611,7 @@ Region = NTSC-J Serial = SCPS-15084 Name = Ratchet & Clank 3 Region = NTSC-J +MemCardFilter = SCPS-15084/SCPS-15056/SCPS-15037 --------------------------------------------- Serial = SCPS-15085 Name = Kenran Butousai @@ -3589,10 +3641,12 @@ Region = NTSC-J Serial = SCPS-15091 Name = Wild ARMs - The 4th Detonator Region = NTSC-J +MemCardFilter = SCAJ-20123/SCPS-15091/SCPS-15092/SCPS-19313/SCPS-19322/SCPS-19323/SCAJ-30002/SCPS-17002/SCPS-19251/SCPS-19253 --------------------------------------------- Serial = SCPS-15092 Name = Wild ARMs - The 4th Detonator Region = NTSC-J +MemCardFilter = SCAJ-20123/SCPS-15091/SCPS-15092/SCPS-19313/SCPS-19322/SCPS-19323/SCAJ-30002/SCPS-17002/SCPS-19251/SCPS-19253 --------------------------------------------- Serial = SCPS-15093 Name = Rule of Rose @@ -3616,6 +3670,7 @@ Serial = SCPS-15097 Name = Wanda to Kyozou (Shadow of the Colossus) Region = NTSC-J Compat = 5 +MemCardFilter = SCAJ-20146/SCAJ-20196/SCAJ-20099/SCPS-11003/SCPS-19103/SCPS-19151/SCPS-55001 --------------------------------------------- Serial = SCPS-15098 Name = Formula One 2005 @@ -3727,6 +3782,7 @@ Region = NTSC-J Compat = 5 //eeClampMode = 3 // Text in races works vuClampMode = 2 // Text in GT mode works +MemCardFilter = SCAJ-20066/SCAJ-30006/SCAJ-30007/SCAJ-30008/SCPS-15055/SCPS-17001/SCPS-19252/SCPS-19304/SCPS-15009/SCPS-55007 --------------------------------------------- Serial = SCPS-17002 Name = Wild ARMs - Alter Code F @@ -3855,6 +3911,7 @@ Name = Gran Turismo 4 [PlayStation 2 The Best] Region = NTSC-J //eeClampMode = 3 // Text in races works vuClampMode = 2 // Text in GT mode works +MemCardFilter = SCAJ-20066/SCAJ-30006/SCAJ-30007/SCAJ-30008/SCPS-15055/SCPS-17001/SCPS-19252/SCPS-19304/SCPS-15009/SCPS-55007 --------------------------------------------- Serial = SCPS-19253 Name = Wild ARMs - Alter Code F [PlayStation 2 The Best - Reprint] @@ -3877,6 +3934,7 @@ Name = Gran Turismo 4 - Prologue [PlayStation 2 The Best] Region = NTSC-J //eeClampMode = 3 // Text in races works vuClampMode = 2 // Text in GT mode works +MemCardFilter = SCAJ-20066/SCAJ-30006/SCAJ-30007/SCAJ-30008/SCPS-15055/SCPS-17001/SCPS-19252/SCPS-19304/SCPS-15009/SCPS-55007 --------------------------------------------- Serial = SCPS-19305 Name = Siren [PlayStation 2 The Best] @@ -3913,6 +3971,7 @@ Region = NTSC-J Serial = SCPS-19313 Name = Wild ARMs - The 4th Detonator [PlayStation 2 The Best] Region = NTSC-J +MemCardFilter = SCAJ-20123/SCPS-15091/SCPS-15092/SCPS-19313/SCPS-19322/SCPS-19323/SCAJ-30002/SCPS-17002/SCPS-19251/SCPS-19253 --------------------------------------------- Serial = SCPS-19315 Name = EyeToy - Play [PlayStation 2 The Best] @@ -3937,6 +3996,7 @@ Region = NTSC-J Serial = SCPS-19320 Name = Wanda to Kyozou (Shadow of the Colossus) [PlayStation 2 The Best] Region = NTSC-J +MemCardFilter = SCAJ-20146/SCAJ-20196/SCAJ-20099/SCPS-11003/SCPS-19103/SCPS-19151/SCPS-55001 --------------------------------------------- Serial = SCPS-19321 Name = Ratchet & Clank 4th - GiriGiri Ginga no Giga Battle [PlayStation 2 The Best] @@ -3945,10 +4005,12 @@ Region = NTSC-J Serial = SCPS-19322 Name = Wild ARMs - The 4th Detonator [PlayStation 2 The Best - Reprint] Region = NTSC-J +MemCardFilter = SCAJ-20123/SCPS-15091/SCPS-15092/SCPS-19313/SCPS-19322/SCPS-19323/SCAJ-30002/SCPS-17002/SCPS-19251/SCPS-19253 --------------------------------------------- Serial = SCPS-19323 Name = Wild ARMs - The 4th Detonator [PlayStation 2 The Best - Reprint] Region = NTSC-J +MemCardFilter = SCAJ-20123/SCPS-15091/SCPS-15092/SCPS-19313/SCPS-19322/SCPS-19323/SCAJ-30002/SCPS-17002/SCPS-19251/SCPS-19253 --------------------------------------------- Serial = SCPS-19324 Name = Tourist Trophy - The Real Riding Simulator [PlayStation 2 The Best] @@ -4158,6 +4220,7 @@ Region = NTSC-J Serial = SCPS-55029 Name = dot Hack - Vol.1 Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233 --------------------------------------------- Serial = SCPS-55030 Name = First Step Victorious Boxers [PlayStation 2 The Best] @@ -4186,6 +4249,7 @@ Region = NTSC-J Serial = SCPS-55042 Name = dot Hack - Vol.2 Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233 --------------------------------------------- Serial = SCPS-55043 Name = Fatal Frame @@ -4955,6 +5019,7 @@ Serial = SCUS-97268 Name = Ratchet & Clank - Going Commando Region = NTSC-U Compat = 5 +MemCardFilter = SCUS-97268/SCUS-97199 --------------------------------------------- Serial = SCUS-97269 Name = Final Fantasy XI [Disc2of2] @@ -5086,6 +5151,8 @@ Region = NTSC-U Compat = 5 //eeClampMode = 3 // Text in races works vuClampMode = 2 // Text in GT mode works +// allows car imports from GT3 or something +MemCardFilter = SCUS-97328/SCUS-97436/SCUS-97102/SCUS-97219/SCUS-97512 --------------------------------------------- Serial = SCUS-97329 Name = Downhill Domination [Demo] @@ -5161,6 +5228,7 @@ Serial = SCUS-97353 Name = Ratchet & Clank - Up Your Arsenal Region = NTSC-U Compat = 5 +MemCardFilter = SCUS-97353/SCUS-97268/SCUS-97199 --------------------------------------------- Serial = SCUS-97355 Name = Siren @@ -5397,6 +5465,7 @@ Serial = SCUS-97429 Name = Jak X - Combat Racing Region = NTSC-U Compat = 5 +MemCardFilter = SCUS-97429/SCUS-97465 --------------------------------------------- Serial = SCUS-97430 Name = Hot Shots Golf FORE! [Regular Demo] @@ -5415,6 +5484,7 @@ Name = Gran Turismo 4 [Online Public Beta] Region = NTSC-U //eeClampMode = 3 // Text in races works vuClampMode = 2 // Text in GT mode works +MemCardFilter = SCUS-97328/SCUS-97436/SCUS-97102/SCUS-97219/SCUS-97512 --------------------------------------------- Serial = SCUS-97437 Name = ATV Off-Road Fury 3 [Demo] @@ -5546,6 +5616,7 @@ Serial = SCUS-97472 Name = Shadow of the Colossus Region = NTSC-U Compat = 5 +MemCardFilter = SCUS-97472/SCUS-97113 --------------------------------------------- Serial = SCUS-97473 Name = World Tour Soccer 2006 [Demo] @@ -6162,6 +6233,7 @@ Region = NTSC-Unk Serial = SLAJ-25066 Name = Burnout Revenge - Battle Racing Ignited Region = NTSC-Unk +MemCardFilter = SLAJ-25066/SLPM-66108/SLPM-66652/SLPM-65719/SLPM-65958/SLPM-66962/SLAJ-25053/SLPM-66204 --------------------------------------------- Serial = SLAJ-25068 Name = Shin Sangoku Musou 4 - Moushouden @@ -6178,6 +6250,7 @@ Region = NTSC-Unk Serial = SLAJ-25075 Name = Need for Speed - Most Wanted [Black Edition] Region = NTSC-Unk +MemCardFilter = SLAJ-25075/SLPM-66232/SLPM-66562/SLPM-65766/SLPM-66051/SLPM-66960 --------------------------------------------- Serial = SLAJ-25076 Name = Harry Potter and the Goblet of Fire @@ -6186,6 +6259,7 @@ Region = NTSC-Unk Serial = SLAJ-25077 Name = Sengoku Musou 2 Region = NTSC-Unk +MemCardFilter = SLAJ-25077/SLPM-55122/SLPM-66307/SLPM-74247/SLAJ-25035/SLPM-65517/SLPM-74212/SLPM-74235/SLAJ-25048/SLPM-65718/SLPM-74224/SLPM-74249 --------------------------------------------- Serial = SLAJ-25078 Name = Black @@ -6550,6 +6624,8 @@ Compat = 5 Serial = SLES-50054 Name = Midnight Club Region = PAL-Unk +// reads Smuggler's Run for bonus unlockable +MemCardFilter = SLES-50054/SLES-50071/SLES-50055/SLES-50061 --------------------------------------------- Serial = SLES-50055 Name = Smuggler's Run @@ -6597,6 +6673,7 @@ Serial = SLES-50071 Name = Midnight Club - Street Racing Region = PAL-Unk Compat = 3 +MemCardFilter = SLES-50054/SLES-50071/SLES-50055/SLES-50061 --------------------------------------------- Serial = SLES-50072 Name = Street Fighter EX3 @@ -9444,6 +9521,7 @@ Serial = SLES-51434 Name = Silent Hill 3 Region = PAL-M5 Compat = 5 +MemCardFilter = SLES-51434/SLES-50382/SLES-51156 --------------------------------------------- Serial = SLES-51435 Name = International Superstar Soccer 3 @@ -10421,11 +10499,13 @@ Region = PAL-M4 Serial = SLES-51913 Name = Onimusha Blade Warrior Region = PAL-Unk +MemCardFilter = SLES-51913/SLES-51914 --------------------------------------------- Serial = SLES-51914 Name = Onimusha 3 - Demon Siege Region = PAL-Unk Compat = 5 +MemCardFilter = SLES-51913/SLES-51914 --------------------------------------------- Serial = SLES-51915 Name = Pro Evolution Soccer 3 @@ -11014,6 +11094,7 @@ EETimingHack = 1 //Path 3 masking errors Serial = SLES-52237 Name = dot Hack - Part 1 - Infection Region = PAL-M5 +MemCardFilter = SLES-52237/SLES-52467/SLES-52468/SLES-52469 --------------------------------------------- Serial = SLES-52238 Name = Nightshade @@ -11471,16 +11552,19 @@ Serial = SLES-52467 Name = dot Hack - Part 2 - Mutation Region = PAL-M5 Compat = 5 +MemCardFilter = SLES-52237/SLES-52467/SLES-52468/SLES-52469 --------------------------------------------- Serial = SLES-52468 Name = dot Hack - Part 4 - Quarantine Region = PAL-M5 Compat = 5 +MemCardFilter = SLES-52237/SLES-52467/SLES-52468/SLES-52469 --------------------------------------------- Serial = SLES-52469 Name = dot Hack - Part 3 - Outbreak Region = PAL-M5 Compat = 5 +MemCardFilter = SLES-52237/SLES-52467/SLES-52468/SLES-52469 --------------------------------------------- Serial = SLES-52471 Name = Naval Ops - Commander @@ -12782,6 +12866,8 @@ patch=1,EE,0010f3e0,word,00000000 Serial = SLES-52988 Name = MegaMan X8 Region = PAL-Unk +// reads Command Mission save for bonus boss +MemCardFilter = SLES-52988/SLES-52832 --------------------------------------------- Serial = SLES-52989 Name = Blowout @@ -12795,6 +12881,8 @@ Serial = SLES-52998 Name = Sonic Mega Collection Plus Region = PAL-Unk Compat = 5 +// two games unlock by having a Sonic Heroes save +MemCardFilter = SLES-52998/SLES-51950 --------------------------------------------- Serial = SLES-52999 Name = RC Toy Machines @@ -12972,6 +13060,8 @@ Serial = SLES-53039 Name = Champions - Return to Arms // aka "Champions of Norrath 2" Region = PAL-Unk Compat = 4 +// allows import of characters from first game +MemCardFilter = SLES-53039/SLES-52325 --------------------------------------------- Serial = SLES-53041 Name = RTL Ski Alpine 2005 @@ -13481,6 +13571,8 @@ Region = PAL-M6 Serial = SLES-53319 Name = Resident Evil Outbreak - File #2 Region = PAL-Unk +// reads and writes to Outbreak File #1 +MemCardFilter = SLES-53319/SCES-50987/SLES-51589 --------------------------------------------- Serial = SLES-53320 Name = S.C.A.R. - Squadra Corse Alfa Romeo @@ -13541,6 +13633,8 @@ Serial = SLES-53350 Name = Sonic Gems Collection Region = PAL-E Compat = 5 +// Vectorman unlocks by having a Mega Collection or Heroes save +MemCardFilter = SLES-53350/SLES-52998/SLES-51950 --------------------------------------------- Serial = SLES-53351 Name = SSX On Tour @@ -13892,11 +13986,13 @@ Serial = SLES-53506 Name = Burnout Revenge Region = PAL-M5 Compat = 5 +MemCardFilter = SLES-53506/SLES-53507/SLES-52584/SLES-52585/SLES-53510 --------------------------------------------- Serial = SLES-53507 Name = Burnout Revenge Region = PAL-Unk Compat = 5 +MemCardFilter = SLES-53506/SLES-53507/SLES-52584/SLES-52585/SLES-53510 --------------------------------------------- Serial = SLES-53508 Name = Ultimate Pro-Pinball @@ -13934,14 +14030,17 @@ Region = PAL-G Serial = SLES-53526 Name = Suffering, The - Ties that Bind Region = PAL-E-F +MemCardFilter = SLES-53526/SLES-53527/SLES-53528/SLES-53626/SLES-51693/SLES-52439/SLES-52531 --------------------------------------------- Serial = SLES-53527 Name = Suffering, The - Ties that Bind Region = PAL-E-I-S +MemCardFilter = SLES-53526/SLES-53527/SLES-53528/SLES-53626/SLES-51693/SLES-52439/SLES-52531 --------------------------------------------- Serial = SLES-53528 Name = Suffering, The - Ties that Bind Region = PAL-G +MemCardFilter = SLES-53526/SLES-53527/SLES-53528/SLES-53626/SLES-51693/SLES-52439/SLES-52531 --------------------------------------------- Serial = SLES-53529 Name = FIFA '06 @@ -14032,14 +14131,18 @@ Serial = SLES-53557 Name = Need for Speed - Most Wanted Region = PAL-E Compat = 5 +// reads Underground 2 save for extra money +MemCardFilter = SLES-53557/SLES-53558/SLES-53559/SLES-53857/SLES-52725 --------------------------------------------- Serial = SLES-53558 Name = Need for Speed - Most Wanted Region = PAL-Unk +MemCardFilter = SLES-53557/SLES-53558/SLES-53559/SLES-53857/SLES-52725 --------------------------------------------- Serial = SLES-53559 Name = Need for Speed - Most Wanted Region = PAL-Unk +MemCardFilter = SLES-53557/SLES-53558/SLES-53559/SLES-53857/SLES-52725 --------------------------------------------- Serial = SLES-53560 Name = Sonic Riders @@ -14179,6 +14282,7 @@ Compat = 5 Serial = SLES-53626 Name = Suffering, The - Ties that Bind Region = PAL-M2 +MemCardFilter = SLES-53526/SLES-53527/SLES-53528/SLES-53626/SLES-51693/SLES-52439/SLES-52531 --------------------------------------------- Serial = SLES-53635 Name = NASCAR '06 - Total Team Control @@ -14465,6 +14569,8 @@ Name = Castlevania - Curse of Darkness Region = PAL-Unk Compat = 5 vuClampMode = 0 //SPS with microVU +// reads Lament of Innocence save for easter egg +MemCardFilter = SLES-53755/SLES-52118 --------------------------------------------- Serial = SLES-53756 Name = Resident Evil 4 @@ -14506,6 +14612,7 @@ Compat = 5 Serial = SLES-53769 Name = Suikoden Tactics Region = PAL-Unk +MemCardFilter = SLES-53769/SLES-52913 --------------------------------------------- Serial = SLES-53772 Name = Air Raid 3 @@ -14658,6 +14765,7 @@ Region = PAL-Unk Serial = SLES-53857 Name = Need for Speed - Most Wanted [Black Edition] Region = PAL-E +MemCardFilter = SLES-53557/SLES-53558/SLES-53559/SLES-53857/SLES-52725 --------------------------------------------- Serial = SLES-53860 Name = Dynasty Warriors 5 - Xtreme Legends @@ -15440,6 +15548,8 @@ Region = PAL-F Serial = SLES-54221 Name = LEGO Star Wars II - The Original Trilogy Region = PAL-Unk +// allows import of characters from Lego Star Wars 1 +MemCardFilter = SLES-54221/SLES-53194 --------------------------------------------- Serial = SLES-54222 Name = SuperBikes Riding Challenge @@ -15639,6 +15749,8 @@ Region = PAL-E Serial = SLES-54340 Name = Samurai Warriors 2 Region = PAL-Unk +// reads Samurai Warriors and Samurai Warriors Xtreme Legends saves for unlockables +MemCardFilter = SLES-54340/SLES-52551/SLES-52552/SLES-52553/SLES-52554/SLES-52555/SLES-53002/SLES-53003/SLES-53004 --------------------------------------------- Serial = SLES-54341 Name = Dancing Stage SuperNOVA @@ -16077,6 +16189,8 @@ Serial = SLES-54555 Name = Shin Megami Tensei: Digital Devil Saga 2 Region = PAL-E EETimingHack = 1 +// reads data from DDS1 +MemCardFilter = SLES-54555/SLES-53458 --------------------------------------------- Serial = SLES-54560 Name = Alpine Ski Racing 2007 @@ -16915,6 +17029,8 @@ Region = PAL-M5 Serial = SLES-55242 Name = Yakuza 2 Region = PAL-E +// allows import of Yakuza 1 data +MemCardFilter = SLES-55242/SLES-54171 --------------------------------------------- Serial = SLES-55243 Name = FIFA 09 @@ -17022,6 +17138,7 @@ Serial = SLES-55354 Name = Shin Megami Tensei: Persona 3 FES Region = PAL-E VuClipFlagHack = 1 +MemCardFilter = SLES-55354/SLES-55018 --------------------------------------------- Serial = SLES-55355 Name = Guitar Hero - World Tour @@ -17427,6 +17544,7 @@ Serial = SLES-82012 Name = Devil May Cry 2 [Lucia Disc] Region = PAL-E Compat = 5 +MemCardFilter = SLES-82011 --------------------------------------------- Serial = SLES-82013 Name = Metal Gear Solid 3 - Snake Eater @@ -17440,6 +17558,7 @@ Region = PAL-E-F-S Serial = SLES-82019 Name = Cy Girls [Disc 2] Region = PAL-E-F-S +MemCardFilter = SLES-82018 --------------------------------------------- Serial = SLES-82020 Name = Cy Girls [Disc 1] @@ -17448,6 +17567,7 @@ Region = PAL-E-G-I Serial = SLES-82021 Name = Cy Girls [Disc 2] Region = PAL-E-G-I +MemCardFilter = SLES-82020 --------------------------------------------- Serial = SLES-82024 Name = Metal Gear Solid 3 - Snake Eater @@ -17468,6 +17588,7 @@ Name = Star Ocean 3 - Till the End of Time [Disc2of2] Region = PAL-E Compat = 5 VuAddSubHack = 1 +MemCardFilter = SLES-82028 --------------------------------------------- Serial = SLES-82030 Name = Shadow Hearts - Covenant [Disc1of2] @@ -17476,6 +17597,7 @@ Region = PAL-Unk Serial = SLES-82031 Name = Shadow Hearts - Covenant [Disc2of2] Region = PAL-Unk +MemCardFilter = SLES-82030 --------------------------------------------- Serial = SLES-82032 Name = Metal Gear Solid 3 - Snake Eater @@ -17484,10 +17606,12 @@ Region = PAL-G Serial = SLES-82034 Name = Xenosaga Episode II [Disc1of2] Region = PAL-E +MemCardFilter = SLES-82034/SCES-82034 --------------------------------------------- Serial = SLES-82035 Name = Xenosaga Episode II [Disc2of2] Region = PAL-E +MemCardFilter = SLES-82034/SCES-82034 --------------------------------------------- Serial = SLES-82036 Name = Armored Core - Nexus [Evolution Disc] @@ -17510,6 +17634,7 @@ Serial = SLES-82039 Name = Onimusha - Dawn of Dreams [Disc2] Region = PAL-Unk Compat = 5 +MemCardFilter = SLES-82038 [patches = 812C5A96] comment= patch by Shadow Lady patch=0,EE,00104170,word,00000000 @@ -17522,6 +17647,7 @@ Region = PAL-Unk Serial = SLES-82043 Name = Metal Gear Solid 3 - Subsistence [Disc2of3] Region = PAL-Unk +MemCardFilter = SLES-82042 --------------------------------------------- Serial = SLES-82044 Name = Metal Gear Solid 3 - Subsistence [Disc1of3] @@ -17530,6 +17656,7 @@ Region = PAL-Unk Serial = SLES-82045 Name = Metal Gear Solid 3 - Subsistence [Disc2of3] Region = PAL-Unk +MemCardFilter = SLES-82044 --------------------------------------------- Serial = SLES-82046 Name = Metal Gear Solid 3 - Subsistence [Disc1of3] @@ -17538,6 +17665,7 @@ Region = PAL-Unk Serial = SLES-82047 Name = Metal Gear Solid 3 - Subsistence [Disc2of3] Region = PAL-Unk +MemCardFilter = SLES-82046 --------------------------------------------- Serial = SLES-82048 Name = Metal Gear Solid 3 - Subsistence [Disc1of3] @@ -17546,22 +17674,27 @@ Region = PAL-Unk Serial = SLES-82049 Name = Metal Gear Solid 3 - Subsistence [Disc2of3] Region = PAL-Unk +MemCardFilter = SLES-82048 --------------------------------------------- Serial = SLES-82050 Name = Metal Gear Solid 3 - Subsistence [Disc3of3] Region = PAL-Unk +MemCardFilter = SLES-82042 --------------------------------------------- Serial = SLES-82051 Name = Metal Gear Solid 3 - Subsistence [Disc3of3] Region = PAL-Unk +MemCardFilter = SLES-82044 --------------------------------------------- Serial = SLES-82052 Name = Metal Gear Solid 3 - Subsistence [Disc3of3] Region = PAL-Unk +MemCardFilter = SLES-82046 --------------------------------------------- Serial = SLES-82053 Name = Metal Gear Solid 3 - Subsistence [Disc3of3] Region = PAL-Unk +MemCardFilter = SLES-82048 --------------------------------------------- Serial = SLKA-15003 Name = Shikigami no Shiro II @@ -17606,6 +17739,7 @@ Serial = SLKA-25013 Name = Devil May Cry 2 Lucia Region = NTSC-K Compat = 5 +MemCardFilter = SLKA-25012 --------------------------------------------- Serial = SLKA-25015 Name = Evolution Skateboarding @@ -17733,10 +17867,12 @@ Region = NTSC-K Serial = SLKA-25092 Name = Onimusha Buraiden Region = NTSC-K +MemCardFilter = SLKA-25092/SLKA-25093 --------------------------------------------- Serial = SLKA-25093 Name = Onimusha 3 Demon Siege Region = NTSC-K +MemCardFilter = SLKA-25092/SLKA-25093 --------------------------------------------- Serial = SLKA-25100 Name = Dragon Quest V Dragon Quarter @@ -18049,10 +18185,12 @@ Region = NTSC-K Serial = SLKA-25280 Name = Ryu ga Gotoku 2 [Disc1of2] Region = NTSC-K +MemCardFilter = SLKA-25280/SLKA-25342 --------------------------------------------- Serial = SLKA-25281 Name = Ryu ga Gotoku 2 [Disc2of2] Region = NTSC-K +MemCardFilter = SLKA-25280/SLKA-25342 --------------------------------------------- Serial = SLKA-25284 Name = Sakura Taisen 3 @@ -18102,6 +18240,7 @@ Name = Digital Devil Saga: Avatar Tuner 2 Region = NTSC-K Compat = 5 EETimingHack = 1 +MemCardFilter = SLKA-25301/SLKA-25300 --------------------------------------------- Serial = SLKA-25307 Name = Dragonball Z Sparking @@ -18144,6 +18283,7 @@ Name = Castlevania - Curse of Darkness Region = NTSC-K Compat = 5 vuClampMode = 0 //SPS with microVU +MemCardFilter = SLKA-25328/SLKA-25082 --------------------------------------------- Serial = SLKA-25329 Name = Shin Sangoku Musou 4 - Moushouden @@ -18182,6 +18322,7 @@ Serial = SLKA-25354 Name = Metal Gear Solid 3 - Subsistence [Limited Edition] [Disc2of2] Region = NTSC-K Compat = 5 +MemCardFilter = SLKA-25353 --------------------------------------------- Serial = SLKA-25359 Name = Winning Eleven 9 - Liveware Edition @@ -18344,6 +18485,7 @@ Region = NTSC-J Serial = SLPM-55122 Name = Sengoku Musou 2 Region = NTSC-J +MemCardFilter = SLAJ-25077/SLPM-55122/SLPM-66307/SLPM-74247/SLAJ-25035/SLPM-65517/SLPM-74212/SLPM-74235/SLAJ-25048/SLPM-65718/SLPM-74224/SLPM-74249 --------------------------------------------- Serial = SLPM-55127 Name = Need for Speed - Undercover @@ -21058,19 +21200,23 @@ Region = NTSC-J Serial = SLPM-65040 Name = Fear, The [Disc1of4] Region = NTSC-J +MemCardFilter = SLPM-65040/SLPM-65041/SLPM-65042/SLPM-65043 --------------------------------------------- Serial = SLPM-65041 Name = Fear, The [Disc2of4] Region = NTSC-J +MemCardFilter = SLPM-65040/SLPM-65041/SLPM-65042/SLPM-65043 --------------------------------------------- Serial = SLPM-65042 Name = Fear, The [Disc3of4] Region = NTSC-J Compat = 5 +MemCardFilter = SLPM-65040/SLPM-65041/SLPM-65042/SLPM-65043 --------------------------------------------- Serial = SLPM-65043 Name = Fear, The [Disc4of4] Region = NTSC-J +MemCardFilter = SLPM-65040/SLPM-65041/SLPM-65042/SLPM-65043 --------------------------------------------- Serial = SLPM-65044 Name = Aizouban Angelique Trois [Koei The Best] @@ -21151,10 +21297,13 @@ Region = NTSC-J Serial = SLPM-65073 Name = Genso Suikoden III Region = NTSC-J +// This looks like a mess because it includes all serials for Suikoden 3, Suikoden 2, Suikogaiden 1, and Suikogaiden 2. A lot of these probably aren't actually required but it's not really hurting anything to have them here. +MemCardFilter = SLPM-65073/SLPM-65074/SLPM-65305/SLPM-65694/SLPM-86168/SLPM-86389/SLPM-87100/SLPM-86637/SLPM-86883/SLPM-87261/SLPM-86663/SLPM-86953/SLPM-87262 --------------------------------------------- Serial = SLPM-65074 Name = Genso Suikoden III Region = NTSC-J +MemCardFilter = SLPM-65073/SLPM-65074/SLPM-65305/SLPM-65694/SLPM-86168/SLPM-86389/SLPM-87100/SLPM-86637/SLPM-86883/SLPM-87261/SLPM-86663/SLPM-86953/SLPM-87262 --------------------------------------------- Serial = SLPM-65075 Name = K-1 World Grand Prix 2001 @@ -21193,11 +21342,13 @@ Serial = SLPM-65086 Name = Ayumi Hamasaki Dome Tour 2001 - A Visual Mix [Disc1of2] Region = NTSC-J Compat = 5 +MemCardFilter = SLPS-25071/SLPS-25072/SLPM-65086/SLPM-65087 --------------------------------------------- Serial = SLPM-65087 Name = Ayumi Hamasaki Dome Tour 2001 - A Visual Mix [Disc2of2] Region = NTSC-J Compat = 5 +MemCardFilter = SLPS-25071/SLPS-25072/SLPM-65086/SLPM-65087 --------------------------------------------- Serial = SLPM-65089 Name = Grandia Extreme @@ -21634,6 +21785,7 @@ Serial = SLPM-65233 Name = Devil May Cry 2 [Lucia Disc] Region = NTSC-J Compat = 5 +MemCardFilter = SLPM-65232 --------------------------------------------- Serial = SLPM-65234 Name = Gregory Horror Show - Soul Collector @@ -21715,6 +21867,7 @@ Serial = SLPM-65257 Name = Silent Hill 3 Region = NTSC-J Compat = 5 +MemCardFilter = SLPM-65257/SLPM-65622/SLPM-66018/SLPM-65051/SLPM-65098/SLPM-65341/SLPM-65631 --------------------------------------------- Serial = SLPM-65260 Name = Air Land Force @@ -21879,6 +22032,7 @@ Region = NTSC-J Serial = SLPM-65305 Name = Genso Suikoden 3 Region = NTSC-J +MemCardFilter = SLPM-65073/SLPM-65074/SLPM-65305/SLPM-65694/SLPM-86168/SLPM-86389/SLPM-87100/SLPM-86637/SLPM-86883/SLPM-87261/SLPM-86663/SLPM-86953/SLPM-87262 --------------------------------------------- Serial = SLPM-65306 Name = Sakura Yuki Gekka [Limited Edition] @@ -22263,6 +22417,7 @@ Serial = SLPM-65411 Name = Onimusha Buraiden Region = NTSC-J Compat = 5 +MemCardFilter = SLPM-65411/SLPM-65413 --------------------------------------------- Serial = SLPM-65412 Name = War of the Monsters @@ -22271,6 +22426,7 @@ Region = NTSC-J Serial = SLPM-65413 Name = Onimusha 3 Region = NTSC-J +MemCardFilter = SLPM-65411/SLPM-65413 --------------------------------------------- Serial = SLPM-65414 Name = Simple 2000 Series Vol.45 - The Love and Tears and Memory @@ -22365,6 +22521,7 @@ Name = Star Ocean 3 [Director's Cut] [Disc2of2] Region = NTSC-J Compat = 5 VuAddSubHack = 1 +MemCardFilter = SLPM-65438 --------------------------------------------- Serial = SLPM-65441 Name = Ashita no Joe: Masshiro ni Moetsukuru @@ -22595,14 +22752,17 @@ Compat = 5 Serial = SLPM-65504 Name = Cool Girl [Limited Edition] [Disc1of2] Region = NTSC-J +MemCardFilter = SLPM-65504/SLPM-65505/SLPM-65506/SLPM-65742 --------------------------------------------- Serial = SLPM-65505 Name = Cool Girl [Limited Edition] [Disc2of2] Region = NTSC-J +MemCardFilter = SLPM-65504/SLPM-65505/SLPM-65506/SLPM-65742 --------------------------------------------- Serial = SLPM-65506 Name = Cool Girl Region = NTSC-J +MemCardFilter = SLPM-65504/SLPM-65505/SLPM-65506/SLPM-65742 --------------------------------------------- Serial = SLPM-65508 Name = Pop'n Music 9 @@ -23029,6 +23189,7 @@ Compat = 5 Serial = SLPM-65622 Name = Silent Hill 3 [Konami The Best] Region = NTSC-J +MemCardFilter = SLPM-65257/SLPM-65622/SLPM-66018/SLPM-65051/SLPM-65098/SLPM-65341/SLPM-65631 --------------------------------------------- Serial = SLPM-65623 Name = Tantei Gakuen Q: Kiokan no Satsui [Konami The Best] @@ -23274,6 +23435,7 @@ Serial = SLPM-65692 Name = BioHazard Outbreak - File #2 Region = NTSC-J Compat = 5 +MemCardFilter = SLPM-65692/SLPM-65428/SLPM-74201 --------------------------------------------- Serial = SLPM-65693 Name = Tokimeki Memorial 3 [Konami Palace Selection] @@ -23282,6 +23444,7 @@ Region = NTSC-J Serial = SLPM-65694 Name = Genso Suikoden 3 [Konami Dendou Collection] Region = NTSC-J +MemCardFilter = SLPM-65073/SLPM-65074/SLPM-65305/SLPM-65694/SLPM-86168/SLPM-86389/SLPM-87100/SLPM-86637/SLPM-86883/SLPM-87261/SLPM-86663/SLPM-86953/SLPM-87262 --------------------------------------------- Serial = SLPM-65695 Name = Hard Luck - Return of the Heroes @@ -23408,6 +23571,7 @@ Region = NTSC-J Serial = SLPM-65730 Name = RockMan X8 Region = NTSC-J +MemCardFilter = SLPM-65730/SLPM-65643 --------------------------------------------- Serial = SLPM-65731 Name = Tom Clancy's Rainbow Six 3 @@ -23454,6 +23618,7 @@ Region = NTSC-J Serial = SLPM-65742 Name = Cool Girl [Konami the Best] Region = NTSC-J +MemCardFilter = SLPM-65504/SLPM-65505/SLPM-65506/SLPM-65742 --------------------------------------------- Serial = SLPM-65744 Name = Firefighter F.D. 18 [Konami The Best] @@ -23510,6 +23675,7 @@ Region = NTSC-J Serial = SLPM-65758 Name = Sonic Mega Collection Plus Region = NTSC-J +MemCardFilter = SLPM-65758/SLAJ-25027/SLPM-65431 --------------------------------------------- Serial = SLPM-65759 Name = Mr. Incredible @@ -23645,6 +23811,7 @@ Serial = SLPM-65795 Name = Digital Devil Saga: Avatar Tuner 2 Region = NTSC-J EETimingHack = 1 +MemCardFilter = SCAJ-20120/SLPM-65795/SLPM-66373/SCAJ-20095/SLPM-65597/SLPM-66372 --------------------------------------------- Serial = SLPM-65796 Name = Project Altered Beast @@ -24346,6 +24513,7 @@ Serial = SLPM-65977 Name = Grandia III [Disc2of2] Region = NTSC-J Compat = 5 +MemCardFilter = SLPM-65976 --------------------------------------------- Serial = SLPM-65978 Name = Kurogane no Houkou 2 - Warship Gunner [Koei The Best] @@ -24481,6 +24649,7 @@ Region = NTSC-J Serial = SLPM-66018 Name = Silent Hill 3 [Konami Dendou Collection] Region = NTSC-J +MemCardFilter = SLPM-65257/SLPM-65622/SLPM-66018/SLPM-65051/SLPM-65098/SLPM-65341/SLPM-65631 --------------------------------------------- Serial = SLPM-66019 Name = Stuntman @@ -24693,6 +24862,7 @@ Serial = SLPM-66074 Name = Sonic Gems Collection Region = NTSC-J Compat = 5 +MemCardFilter = SLPM-66074/SLPM-65758/SLAJ-25027/SLPM-65431 --------------------------------------------- Serial = SLPM-66076 Name = Meine Liebe - Yuubinaru kioku [Konami the Best] @@ -24795,6 +24965,8 @@ Region = NTSC-J Serial = SLPM-66105 Name = Rhapsodia Region = NTSC-J +// this is the JP Suikoden Tactics +MemCardFilter = SLPM-66105/SLPM-66594/SLPM-65599/SLPM-65600 --------------------------------------------- Serial = SLPM-66106 Name = Hoshi no Furu Koku [Limited Edition] @@ -24807,6 +24979,7 @@ Region = NTSC-J Serial = SLPM-66108 Name = Burnout Revenge Region = NTSC-J +MemCardFilter = SLAJ-25066/SLPM-66108/SLPM-66652/SLPM-65719/SLPM-65958/SLPM-66962/SLAJ-25053/SLPM-66204 --------------------------------------------- Serial = SLPM-66109 Name = Code Age Commanders @@ -25021,6 +25194,7 @@ Name = Akumajo Dracula - Yami no Juin Region = NTSC-J Compat = 5 vuClampMode = 0 //SPS with microVU +MemCardFilter = SLPM-66175/SLPM-66668/SLPM-65406/SLPM-65444/SLPM-66325 --------------------------------------------- Serial = SLPM-66176 Name = Chaos Field - New Order @@ -25186,22 +25360,27 @@ Region = NTSC-J Serial = SLPM-66220 Name = Metal Gear Solid 3 - Subsistence [First Print Limited Edition] [Disc1of3] Region = NTSC-J +MemCardFilter = SLPM-66117 --------------------------------------------- Serial = SLPM-66221 Name = Metal Gear Solid 3 - Subsistence [First Print Limited Edition] [Disc2of3] Region = NTSC-J +MemCardFilter = SLPM-66117 --------------------------------------------- Serial = SLPM-66222 Name = Metal Gear Solid 3 - Subsistence [First Print Limited Edition] [Disc3of3] Region = NTSC-J +MemCardFilter = SLPM-66117 --------------------------------------------- Serial = SLPM-66223 Name = Metal Gear Solid 3 - Subsistence [Disc1of2] Region = NTSC-J +MemCardFilter = SLPM-66117 --------------------------------------------- Serial = SLPM-66224 Name = Metal Gear Solid 3 - Subsistence [Disc2of2] Region = NTSC-J +MemCardFilter = SLPM-66117 --------------------------------------------- Serial = SLPM-66225 Name = Da Capo Four Seasons [Deluxe Pack] @@ -25230,6 +25409,7 @@ Region = NTSC-J Serial = SLPM-66232 Name = Need for Speed - Most Wanted Region = NTSC-J +MemCardFilter = SLAJ-25075/SLPM-66232/SLPM-66562/SLPM-65766/SLPM-66051/SLPM-66960 --------------------------------------------- Serial = SLPM-66233 Name = Kingdom Hearts II @@ -25407,6 +25587,7 @@ Serial = SLPM-66276 Name = Shin Onimusha - Dawn of Dreams [Disc2of2] Region = NTSC-J Compat = 2 +MemCardFilter = SLPM-66275 [patches = BD17248E] comment= patch by Shadow Lady patch=0,EE,00104170,word,00000000 @@ -25521,6 +25702,7 @@ Region = NTSC-J Serial = SLPM-66307 Name = Sengoku Musou 2 Region = NTSC-J +MemCardFilter = SLAJ-25077/SLPM-55122/SLPM-66307/SLPM-74247/SLAJ-25035/SLPM-65517/SLPM-74212/SLPM-74235/SLAJ-25048/SLPM-65718/SLPM-74224/SLPM-74249 --------------------------------------------- Serial = SLPM-66308 Name = Harukanaru Toki no Naka Premium Boxset @@ -25718,6 +25900,7 @@ Serial = SLPM-66373 Name = Digital Devil Saga: Avatar Tuner 2 [Atlus Best Collection] Region = NTSC-J EETimingHack = 1 +MemCardFilter = SCAJ-20120/SLPM-65795/SLPM-66373/SCAJ-20095/SLPM-65597/SLPM-66372 --------------------------------------------- Serial = SLPM-66374 Name = World Soccer Winning Eleven 10 @@ -26114,6 +26297,7 @@ Name = Star Ocean 3 - Till the End of Time [Ultimate Hits] [Disc2of2] Region = NTSC-J Compat = 5 VuAddSubHack = 1 +MemCardFilter = SLPM-66478 --------------------------------------------- Serial = SLPM-66480 Name = Dragon Quest V - Bride of the Sky [Ultimate Hits] @@ -26423,6 +26607,7 @@ Region = NTSC-J Serial = SLPM-66562 Name = Need for Speed - Most Wanted [EA Best Hits] Region = NTSC-J +MemCardFilter = SLAJ-25075/SLPM-66232/SLPM-66562/SLPM-65766/SLPM-66051/SLPM-66960 --------------------------------------------- Serial = SLPM-66563 Name = Nizu no Senritsu [2800 Collection] @@ -26463,6 +26648,7 @@ Region = NTSC-J Serial = SLPM-66572 Name = LEGO Star Wars II - The Original Trilogy Region = NTSC-J +MemCardFilter = SLPM-66572/SLPS-20423 --------------------------------------------- Serial = SLPM-66573 Name = Kurogane no Houkou 2 - Warship Gunner [KOEI Selection] @@ -26538,6 +26724,7 @@ Compat = 5 Serial = SLPM-66594 Name = Rhapsodia [Konami the Best] Region = NTSC-J +MemCardFilter = SLPM-66105/SLPM-66594/SLPM-65599/SLPM-65600 --------------------------------------------- Serial = SLPM-66595 Name = J-League Winning Eleven 10 - Europa League '06-'07 @@ -26562,10 +26749,12 @@ Region = NTSC-J Serial = SLPM-66602 Name = Ryu ga Gotoku 2 [Disc1of2] Region = NTSC-J +MemCardFilter = SLPM-66602/SLPM-74301/SLPM-66168/SLPM-74234/SLPM-74253 --------------------------------------------- Serial = SLPM-66603 Name = Ryu ga Gotoku 2 [Disc2of2] Region = NTSC-J +MemCardFilter = SLPM-66602/SLPM-74301/SLPM-66168/SLPM-74234/SLPM-74253 --------------------------------------------- Serial = SLPM-66604 Name = Galaxy Angel - Moonlit Lovers [Banpresido the Best] @@ -26744,6 +26933,7 @@ Region = NTSC-J Serial = SLPM-66652 Name = Burnout Revenge [EA Best Hits] Region = NTSC-J +MemCardFilter = SLAJ-25066/SLPM-66108/SLPM-66652/SLPM-65719/SLPM-65958/SLPM-66962/SLAJ-25053/SLPM-66204 --------------------------------------------- Serial = SLPM-66653 Name = Akudaikan 3 @@ -26803,6 +26993,7 @@ Serial = SLPM-66668 Name = Akumajo Dracula - Yami no Juin [Konami the Best] Region = NTSC-J vuClampMode = 0 //SPS with microVU +MemCardFilter = SLPM-66175/SLPM-66668/SLPM-65406/SLPM-65444/SLPM-66325 --------------------------------------------- Serial = SLPM-66669 Name = Prince of Tennis, The - Card Hunter [First Print Limited Edition] @@ -26832,11 +27023,14 @@ Serial = SLPM-66675 Name = Kingdom Hearts II - Final Mix + Region = NTSC-J Compat = 5 +// reads Re:Chain data and vice-versa +MemCardFilter = SLPM-66675/SLPM-66676 --------------------------------------------- Serial = SLPM-66676 Name = Kingdom Hearts Re: Chain of Memories Region = NTSC-J Compat = 5 +MemCardFilter = SLPM-66675/SLPM-66676 --------------------------------------------- Serial = SLPM-66677 Name = Final Fantasy X - International [Ultimate Hits] @@ -26850,10 +27044,12 @@ Region = NTSC-J Serial = SLPM-66679 Name = Devil Summoner: Kuzunoha Raidou tai Abaddon Ou [Plus] Region = NTSC-J +MemCardFilter = SLPM-66679/SLPM-66683/SLPM-66246/SLPM-66634 --------------------------------------------- Serial = SLPM-66683 Name = Devil Summoner: Kuzunoha Raidou tai Abaddon Ou Region = NTSC-J +MemCardFilter = SLPM-66679/SLPM-66683/SLPM-66246/SLPM-66634 --------------------------------------------- Serial = SLPM-66685 Name = Seaman 2 - Peking Genjin Ikusei Kit @@ -26876,11 +27072,13 @@ Serial = SLPM-66689 Name = Persona 3 FES [Append Edition] Region = NTSC-J VuClipFlagHack = 1 +MemCardFilter = SLPM-66445/SLPM-66689/SLPM-66690 --------------------------------------------- Serial = SLPM-66690 Name = Persona 3 FES [Independent Starting Version] Region = NTSC-J VuClipFlagHack = 1 +MemCardFilter = SLPM-66445/SLPM-66689/SLPM-66690 --------------------------------------------- Serial = SLPM-66691 Name = Sengoku Basara 2 [CapKore] @@ -28344,6 +28542,7 @@ Region = NTSC-J Serial = SLPM-74247 Name = Sengoku Musou 2 [PlayStation 2 The Best] Region = NTSC-J +MemCardFilter = SLAJ-25077/SLPM-55122/SLPM-66307/SLPM-74247/SLAJ-25035/SLPM-65517/SLPM-74212/SLPM-74235/SLAJ-25048/SLPM-65718/SLPM-74224/SLPM-74249 --------------------------------------------- Serial = SLPM-74248 Name = Monster Hunter G [PlayStation 2 The Best] @@ -28402,6 +28601,7 @@ Region = NTSC-J Serial = SLPM-74301 Name = Ryu ga Gotoku 2 [PlayStation 2 The Best] Region = NTSC-J +MemCardFilter = SLPM-66602/SLPM-74301/SLPM-66168/SLPM-74234/SLPM-74253 --------------------------------------------- Serial = SLPM-74402 Name = Capcom vs. SNK 2 [PlayStation 2 The Best] @@ -28532,10 +28732,12 @@ Region = NTSC-J Serial = SLPS-20018 Name = Stepping Selection [Disc1of2] Region = NTSC-J +MemCardFilter = SLPS-20018/SLPS-20019 --------------------------------------------- Serial = SLPS-20019 Name = Stepping Selection [Disc2of2] Region = NTSC-J +MemCardFilter = SLPS-20018/SLPS-20019 --------------------------------------------- Serial = SLPS-20020 Name = FIFA 2000 World Championship @@ -28694,6 +28896,7 @@ Region = NTSC-J Serial = SLPS-20068 Name = Midnight Club - Street Racing Region = NTSC-J +MemCardFilter = SLPS-20068/SLPS-20067 --------------------------------------------- Serial = SLPS-20071 Name = Game Select 5 @@ -28722,10 +28925,12 @@ Region = NTSC-J Serial = SLPS-20084 Name = Basic Studio [Disc 1] Region = NTSC-J +MemCardFilter = SLPS-20084/SLPS-20085 --------------------------------------------- Serial = SLPS-20085 Name = Basic Studio [Disc 2] Region = NTSC-J +MemCardFilter = SLPS-20084/SLPS-20085 --------------------------------------------- Serial = SLPS-20086 Name = Generation of Chaos [Limited Edition] @@ -30009,10 +30214,12 @@ Region = NTSC-J Serial = SLPS-25071 Name = Visual Mix - Ayumi Hamasaki Dome Tour 2001 [Disc1of2] Region = NTSC-J +MemCardFilter = SLPS-25071/SLPS-25072/SLPM-65086/SLPM-65087 --------------------------------------------- Serial = SLPS-25072 Name = Visual Mix - Ayumi Hamasaki Dome Tour 2001 [Disc2of2] Region = NTSC-J +MemCardFilter = SLPS-25071/SLPS-25072/SLPM-65086/SLPM-65087 --------------------------------------------- Serial = SLPS-25074 Name = Project Zero @@ -30174,6 +30381,7 @@ Region = NTSC-J Serial = SLPS-25121 Name = dot Hack Vol.1 Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233 --------------------------------------------- Serial = SLPS-25122 Name = Mobile Suit Gundam - Lost War Chronicles [Limited Edition] @@ -30235,6 +30443,7 @@ Region = NTSC-J Serial = SLPS-25143 Name = dot Hack Vol.2 - Mutation Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233 --------------------------------------------- Serial = SLPS-25144 Name = Memorial Song @@ -30275,6 +30484,7 @@ Region = NTSC-J Serial = SLPS-25158 Name = dot Hack - Vol.3 - Erosion Pollution Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233 --------------------------------------------- Serial = SLPS-25159 Name = Technic Beat @@ -30417,6 +30627,7 @@ Region = NTSC-J Serial = SLPS-25202 Name = dot Hack - Vol.4 - Zettai Houi Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233 --------------------------------------------- Serial = SLPS-25204 Name = Moto GP3 @@ -30850,6 +31061,7 @@ Region = NTSC-J Serial = SLPS-25335 Name = Shadow Hearts 2 [Disc1of2] Region = NTSC-J +MemCardFilter = SLPS-25334 --------------------------------------------- Serial = SLPS-25336 Name = Bass Landing 3 [with TsuriCon2+] [Sammy Best] @@ -30975,18 +31187,23 @@ Region = NTSC-J Serial = SLPS-25366 Name = Xenosaga Episode II - Jenseits von Gut und Bose [Premium Box] [Disc1of2] Region = NTSC-J +MemCardFilter = SLPS-29001/SLPS-29002/SLPS-29005/SLPS-25368/SLPS-25353/SLPS-73224 --------------------------------------------- Serial = SLPS-25367 Name = Xenosaga Episode II - Jenseits von Gut und Bose [Premium Box] [Disc2of2] Region = NTSC-J +MemCardFilter = SLPS-29001/SLPS-29002/SLPS-29005/SLPS-25368/SLPS-25353/SLPS-73224 --------------------------------------------- Serial = SLPS-25368 Name = Xenosaga Episode II - Jenseits von Gut und Bose [Disc1of2] Region = NTSC-J +// allows import of Xenosaga I, Xenosaga I Reloaded, and Xenosaga Freaks data +MemCardFilter = SLPS-29001/SLPS-29002/SLPS-29005/SLPS-25368/SLPS-25353/SLPS-73224 --------------------------------------------- Serial = SLPS-25369 Name = Xenosaga Episode II - Jenseits von Gut und Bose [Disc2of2] Region = NTSC-J +MemCardFilter = SLPS-29001/SLPS-29002/SLPS-29005/SLPS-25368/SLPS-25353/SLPS-73224 --------------------------------------------- Serial = SLPS-25370 Name = Spawn - Armageddon @@ -31363,6 +31580,7 @@ Region = NTSC-J Compat = 5 vuClampMode = 3 mvuFlagSpeedHack = 0 +MemCardFilter = SCAJ-20135/SLPS-25467/SLPS-73241/SCAJ-20079/SLPS-25360/SLPS-73210/SLPS-73240 --------------------------------------------- Serial = SLPS-25468 Name = Eternal Aselia - The Spirit of Eternity Sword @@ -31989,6 +32207,7 @@ Region = NTSC-J Serial = SLPS-25629 Name = Ace Combat Zero - The Belkan War Region = NTSC-J +MemCardFilter = SCAJ-20173/SLPS-25629/SLPS-73250/SLPS-25052/SLPS-73205/SLPS-73410/SCAJ-20104/SCAJ-20136/SLPS-25418/SLPS-73218 --------------------------------------------- Serial = SLPS-25630 Name = Pro Yakyuu Netsu Star 2006 @@ -32030,11 +32249,14 @@ Serial = SLPS-25640 Name = Xenosaga Episode III - Also Sprach Zarathustra [Disc1of2] Region = NTSC-J Compat = 5 +// allows import of Xenosaga II save data +MemCardFilter = SLPS-25640/SLPS-25368 --------------------------------------------- Serial = SLPS-25641 Name = Xenosaga Episode III - Also Sprach Zarathustra [Disc2of2] Region = NTSC-J Compat = 5 +MemCardFilter = SLPS-25640/SLPS-25368 --------------------------------------------- Serial = SLPS-25642 Name = Super DragonBall Z @@ -32077,6 +32299,7 @@ Compat = 5 Serial = SLPS-25651 Name = dot Hack G.U. Vol.1 - Saitan Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233/SLPS-25651/SLPS-25655/SLPS-25656/SLPS-25756 --------------------------------------------- Serial = SLPS-25653 Name = Cluster Edge [Limited Edition] @@ -32089,10 +32312,12 @@ Region = NTSC-J Serial = SLPS-25655 Name = dot Hack G.U. Vol.2 - Kimi Omou Koe Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233/SLPS-25651/SLPS-25655/SLPS-25656/SLPS-25756 --------------------------------------------- Serial = SLPS-25656 Name = dot Hack - G.U. Vol.3 - Aruku Youna Hayasa de Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233/SLPS-25651/SLPS-25655/SLPS-25656/SLPS-25756 --------------------------------------------- Serial = SLPS-25657 Name = Fighting Beauty Wulong @@ -32478,6 +32703,7 @@ Region = NTSC-J Serial = SLPS-25756 Name = dot Hack G.U. Vol.1 - Saitan [Bandai the Best] Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233/SLPS-25651/SLPS-25655/SLPS-25656/SLPS-25756 --------------------------------------------- Serial = SLPS-25757 Name = Nanatsu Iro - Drops Pure!! [First Print Limited Edition] @@ -32812,12 +33038,15 @@ Serial = SLPS-25841 Name = Tales of Destiny [Director's Cut] [Premium Box] Region = NTSC-J FpuMulHack = 1 +// allows import of non-DC Tales of Destiny data +MemCardFilter = SLPS-25841/SLPS-25842/SLPS-25715 --------------------------------------------- Serial = SLPS-25842 Name = Tales of Destiny [Director's Cut] Region = NTSC-J Compat = 5 FpuMulHack = 1 +MemCardFilter = SLPS-25841/SLPS-25842/SLPS-25715 --------------------------------------------- Serial = SLPS-25843 Name = Tir-Na-Nog @@ -33158,10 +33387,12 @@ eeClampMode = 1 Serial = SLPS-73224 Name = Xenosaga Episode II - Jenseits von Gut und Bose [PlayStation 2 The Best] [Disc1of2] Region = NTSC-J +MemCardFilter = SLPS-29001/SLPS-29002/SLPS-29005/SLPS-25368/SLPS-25353/SLPS-73224 --------------------------------------------- Serial = SLPS-73225 Name = Xenosaga Episode II - Jenseits von Gut und Bose [PlayStation 2 The Best] [Disc2of2] Region = NTSC-J +MemCardFilter = SLPS-29001/SLPS-29002/SLPS-29005/SLPS-25368/SLPS-25353/SLPS-73224 --------------------------------------------- Serial = SLPS-73226 Name = Tenchu Kurenai [PlayStation 2 The Best] @@ -33182,18 +33413,22 @@ Region = NTSC-J Serial = SLPS-73230 Name = dot Hack - Vol.1 & Vol.2 [PlayStation 2 The Best] [Vol.1 Disc] Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233 --------------------------------------------- Serial = SLPS-73231 Name = dot Hack - Vol.1 & Vol.2 [PlayStation 2 The Best] [Vol.2 Disc] Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233 --------------------------------------------- Serial = SLPS-73232 Name = dot Hack - Vol.3 & Vol.4 [PlayStation 2 The Best] [Vol.3 Disc] Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233 --------------------------------------------- Serial = SLPS-73233 Name = dot Hack - Vol.3 & Vol.4 [PlayStation 2 The Best] [Vol.4 Disc] Region = NTSC-J +MemCardFilter = SCPS-55029/SCPS-55042/SCAJ-20004/SCAJ-20024/SLPS-25121/SLPS-25143/SLPS-25158/SLPS-25202/SLPS-73230/SLPS-73231/SLPS-73232/SLPS-73233 --------------------------------------------- Serial = SLPS-73234 Name = Kidou Senshi Z-Gundam - AEUG vs. Titans [PlayStation 2 The Best] @@ -33229,6 +33464,7 @@ Name = Minna Daisuki Katamari Damacy [PlayStation 2 The Best] Region = NTSC-J vuClampMode = 3 mvuFlagSpeedHack = 0 +MemCardFilter = SCAJ-20135/SLPS-25467/SLPS-73241/SCAJ-20079/SLPS-25360/SLPS-73210/SLPS-73240 --------------------------------------------- Serial = SLPS-73242 Name = Tales of Legendia [PlayStation 2 The Best] @@ -33267,6 +33503,7 @@ Region = NTSC-J Serial = SLPS-73250 Name = Ace Combat Zero - The Belkan War [PlayStation 2 The Best] Region = NTSC-J +MemCardFilter = SCAJ-20173/SLPS-25629/SLPS-73250/SLPS-25052/SLPS-73205/SLPS-73410/SCAJ-20104/SCAJ-20136/SLPS-25418/SLPS-73218 --------------------------------------------- Serial = SLPS-73251 Name = Naruto - Narutimett Hero 3 [PlayStation 2 The Best] @@ -33601,6 +33838,7 @@ Serial = SLUS-20063 Name = Midnight Club - Street Racing Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20063/SLUS-20065 --------------------------------------------- Serial = SLUS-20064 Name = Oni @@ -34464,6 +34702,7 @@ Serial = SLUS-20267 Name = dot Hack - Part 1 - Infection Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20267/SLUS-20562/SLUS-20563/SLUS-20564 --------------------------------------------- Serial = SLUS-20268 Name = Star Wars - Racer Revenge @@ -35014,6 +35253,8 @@ Serial = SLUS-20387 Name = Suikoden III Region = NTSC-U Compat = 5 +// allows import of Suikoden II clear data +MemCardFilter = SLUS-20387/SLUS-00958 --------------------------------------------- Serial = SLUS-20388 Name = Fatal Frame @@ -35782,16 +36023,19 @@ Serial = SLUS-20562 Name = dot Hack - Part 2 - Mutation Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20267/SLUS-20562/SLUS-20563/SLUS-20564 --------------------------------------------- Serial = SLUS-20563 Name = dot Hack - Part 3 - Outbreak Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20267/SLUS-20562/SLUS-20563/SLUS-20564 --------------------------------------------- Serial = SLUS-20564 Name = dot Hack - Part 4 - Quarantine Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20267/SLUS-20562/SLUS-20563/SLUS-20564 --------------------------------------------- Serial = SLUS-20565 Name = Champions of Norrath @@ -36041,6 +36285,8 @@ Serial = SLUS-20622 Name = Silent Hill 3 Region = NTSC-U Compat = 5 +// reads Silent Hill 2 for easter egg +MemCardFilter = SLUS-20622/SLUS-20228 --------------------------------------------- Serial = SLUS-20623 Name = Winning Eleven 6 @@ -36063,6 +36309,7 @@ Serial = SLUS-20627 Name = Devil May Cry 2 [Disc2of2] Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20484 --------------------------------------------- Serial = SLUS-20628 Name = Disney's Finding Nemo @@ -36364,6 +36611,7 @@ Serial = SLUS-20694 Name = Onimusha 3 - Demon Siege Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20694/SLUS-20710 --------------------------------------------- Serial = SLUS-20695 Name = Chaos Legion @@ -36432,6 +36680,7 @@ Serial = SLUS-20710 Name = Onimusha - Blade Warriors Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20694/SLUS-20710 --------------------------------------------- Serial = SLUS-20711 Name = Dance Dance Revolution MAX 2 @@ -37071,6 +37320,7 @@ Serial = SLUS-20854 Name = Cy Girls [Disc2of2] Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20697 --------------------------------------------- Serial = SLUS-20855 Name = Destruction Derby Arenas @@ -37124,6 +37374,8 @@ Compat = 5 Serial = SLUS-20865 Name = Backyard Baseball Region = NTSC-U +// reads Backyard Basketball for bonus unlockable +MemCardFilter = SLUS-20865/SLUS-20704 --------------------------------------------- Serial = SLUS-20866 Name = Asterix & Obelix XXL - Kick Buttix @@ -37251,11 +37503,14 @@ Name = Star Ocean 3 - Till the End of Time [Disc2of2] Region = NTSC-U Compat = 5 VuAddSubHack = 1 +MemCardFilter = SLUS-20488 --------------------------------------------- Serial = SLUS-20892 Name = Xenosaga - Episode II - Jenseits von Gut und Bose [Disc1of2] Region = NTSC-U Compat = 5 +// allows import of Xenosaga I data +MemCardFilter = SLUS-20469/SLUS-20892 --------------------------------------------- Serial = SLUS-20893 Name = Way of the Samurai 2 @@ -37370,6 +37625,7 @@ Serial = SLUS-20917 Name = Sonic Mega Collection Plus Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20917/SLUS-20718 --------------------------------------------- Serial = SLUS-20918 Name = Super Monkey Ball Deluxe @@ -37581,6 +37837,7 @@ Serial = SLUS-20960 Name = MegaMan X8 Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20960/SLUS-20903 --------------------------------------------- Serial = SLUS-20961 Name = Neo Contra @@ -37660,6 +37917,7 @@ Serial = SLUS-20973 Name = Champions - Return to Arms // aka "Champions of Norrath 2" Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20973/SLUS-20565 --------------------------------------------- Serial = SLUS-20974 Name = Shin Megami Tensei: Digital Devil Saga @@ -37716,6 +37974,7 @@ Serial = SLUS-20984 Name = Resident Evil Outbreak - File #2 Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20984/SLUS-20765 --------------------------------------------- Serial = SLUS-20985 Name = Under the Skin @@ -38024,6 +38283,7 @@ Serial = SLUS-21044 Name = Shadow Hearts - Covenant [Disc2of2] Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-21041 --------------------------------------------- Serial = SLUS-21045 Name = Conflict Vietnam @@ -38428,6 +38688,7 @@ Serial = SLUS-21133 Name = Xenosaga - Episode II - Jenseits von Gut und Bose [Disc2of2] Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20469/SLUS-20892 --------------------------------------------- Serial = SLUS-21134 Name = Resident Evil 4 @@ -38517,6 +38778,7 @@ Name = Shin Megami Tensei: Digital Devil Saga 2 Region = NTSC-U Compat = 5 EETimingHack = 1 +MemCardFilter = SLUS-21152/SLUS-20974 --------------------------------------------- Serial = SLUS-21153 Name = Dynasty Warriors 5 @@ -38590,6 +38852,7 @@ Name = Castlevania - Curse of Darkness Region = NTSC-U Compat = 5 vuClampMode = 0 //SPS with microVU +MemCardFilter = SLUS-21168/SLUS-20733 --------------------------------------------- Serial = SLUS-21169 Name = Rollercoaster Tycoon - The Peeps @@ -38690,6 +38953,7 @@ Serial = SLUS-21189 Name = Suffering, The - Ties That Bind Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-21189/SLUS-20636 --------------------------------------------- Serial = SLUS-21190 Name = Outlaw Tennis @@ -38891,6 +39155,8 @@ Region = NTSC-U Compat = 5 vuClampMode = 3 mvuFlagSpeedHack = 0 +// allows import of constellations from Katamari Damacy +MemCardFilter = SLUS-21230/SLUS-21008 --------------------------------------------- Serial = SLUS-21231 Name = Sniper Elite @@ -38949,11 +39215,14 @@ Serial = SLUS-21242 Name = Burnout Revenge Region = NTSC-U Compat = 5 +// reads Burnout 3 and NFL 06 saves for unlockables +MemCardFilter = SLUS-21242/SLUS-21050/SLUS-21213 --------------------------------------------- Serial = SLUS-21243 Name = Metal Gear Solid 3 - Subsistence [Disc2of3] Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-21359 --------------------------------------------- Serial = SLUS-21244 Name = Fatal Frame 3 - The Tormented @@ -38964,6 +39233,8 @@ Serial = SLUS-21245 Name = Suikoden Tactics Region = NTSC-U Compat = 5 +// allows import of Suikoden 4 clear data +MemCardFilter = SLUS-21245/SLUS-20979 --------------------------------------------- Serial = SLUS-21246 Name = Charlie and the Chocolate Factory @@ -39024,6 +39295,7 @@ Serial = SLUS-21258 Name = dot Hack - G.U. Vol.1 - Rebirth Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20267/SLUS-20562/SLUS-20563/SLUS-20564/SLUS-21258/SLUS-21488/SLUS-21489/SLUS-21480 --------------------------------------------- Serial = SLUS-21259 Name = Stacked with Daniel Negreanu @@ -39069,6 +39341,7 @@ Serial = SLUS-21267 Name = Need for Speed - Most Wanted Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-21267/SLUS-21351/SLUS-21065 --------------------------------------------- Serial = SLUS-21268 Name = 24 - The Game @@ -39189,6 +39462,8 @@ Serial = SLUS-21292 Name = Wild ARMs 4 Region = NTSC-U Compat = 5 +// allows import of Alter Code F clear data +MemCardFilter = SLUS-21292/SLUS-20937 --------------------------------------------- Serial = SLUS-21293 Name = Metal Saga @@ -39390,6 +39665,7 @@ Serial = SLUS-21335 Name = Hustle, The - Detroit Streets - Kat's Story Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-21334 --------------------------------------------- Serial = SLUS-21336 Name = Zathura @@ -39445,6 +39721,7 @@ Serial = SLUS-21346 Name = Ace Combat Zero - The Belkan War Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-21346/SLUS-20152/SLUS-20851 [patches = 65729657] patch=0,EE,00131EBC,word,48498800 patch=0,EE,00131EC0,word,4B00682C @@ -39475,6 +39752,7 @@ Serial = SLUS-21351 Name = Need for Speed - Most Wanted [Black Edition] Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-21267/SLUS-21351/SLUS-21065 --------------------------------------------- Serial = SLUS-21352 Name = Dai Senryaku VII Exceed @@ -39518,6 +39796,7 @@ Compat = 5 Serial = SLUS-21360 Name = Metal Gear Solid 3 - Subsistence [Disc3of3] Region = NTSC-U +MemCardFilter = SLUS-21359 --------------------------------------------- Serial = SLUS-21361 Name = Devil May Cry 3 - Dante's Awakening [Special Edition] @@ -39529,6 +39808,7 @@ Serial = SLUS-21362 Name = Onimusha - Dawn of Dreams [Disc2] Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-21180 [patches = FFDE85E9] comment= patch by Shadow Lady patch=0,EE,00104170,word,00000000 @@ -39663,6 +39943,8 @@ Serial = SLUS-21389 Name = Xenosaga - Episode III - Also Sprach Zarathustra [Disc1of2] Region = NTSC-U Compat = 5 +// allows import of Xenosaga II save data +MemCardFilter = SLUS-21389/SLUS-20892 --------------------------------------------- Serial = SLUS-21390 Name = Urban Chaos - Riot Response @@ -39758,6 +40040,7 @@ Compat = 5 Serial = SLUS-21409 Name = LEGO Star Wars II - The Original Trilogy Region = NTSC-U +MemCardFilter = SLUS-21409/SLUS-21083 --------------------------------------------- Serial = SLUS-21410 Name = Mortal Kombat - Armageddon @@ -39797,6 +40080,7 @@ Serial = SLUS-21417 Name = Xenosaga - Episode III - Also Sprach Zarathustra [Disc2of2] Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-21389/SLUS-20892 --------------------------------------------- Serial = SLUS-21418 Name = Sprint Cars - Road to Knoxville @@ -40011,6 +40295,7 @@ Serial = SLUS-21462 Name = Samurai Warriors 2 Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-21462/SLUS-20878/SLUS-21080 --------------------------------------------- Serial = SLUS-21463 Name = Tom Clancy's Ghost Recon 2 @@ -40097,6 +40382,7 @@ Serial = SLUS-21480 Name = Dot Hack GU Volume 1 - Rebirth - Terminal Disc Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20267/SLUS-20562/SLUS-20563/SLUS-20564/SLUS-21258/SLUS-21488/SLUS-21489/SLUS-21480 --------------------------------------------- Serial = SLUS-21481 Name = NCAA March Madness '07 @@ -40135,11 +40421,13 @@ Serial = SLUS-21488 Name = dot Hack - G.U. Vol.2 - Reminisce Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20267/SLUS-20562/SLUS-20563/SLUS-20564/SLUS-21258/SLUS-21488/SLUS-21489/SLUS-21480 --------------------------------------------- Serial = SLUS-21489 Name = dot Hack - G.U. Vol.3 - Redemption Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20267/SLUS-20562/SLUS-20563/SLUS-20564/SLUS-21258/SLUS-21488/SLUS-21489/SLUS-21480 --------------------------------------------- Serial = SLUS-21490 Name = Test Drive Unlimited @@ -40600,6 +40888,7 @@ Name = Shin Megami Tensei: Persona 3 FES Region = NTSC-U Compat = 5 VuClipFlagHack = 1 +MemCardFilter = SLUS-21621/SLUS-21569 --------------------------------------------- Serial = SLUS-21622 Name = Bee Movie Game @@ -41266,6 +41555,8 @@ Serial = SLUS-21769 Name = Yakuza 2 Region = NTSC-U Compat = 5 +// allows import of Yakuza 1 data +MemCardFilter = SLUS-21769/SLUS-21348 --------------------------------------------- Serial = SLUS-21770 Name = Madden NFL '09 @@ -41579,6 +41870,8 @@ Serial = SLUS-21845 Name = Shin Megami Tensei: Devil Summoner 2: Raidou Kuzunoha vs. King Abaddon Region = NTSC-U Compat = 5 +// allows import of save from Devil Summoner 1 +MemCardFilter = SLUS-21845/SLUS-21431 --------------------------------------------- Serial = SLUS-21846 Name = Sonic Unleashed From b4b55289d6aad3dc057e89402db79d08d8fc9853 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 25 May 2015 04:40:02 +0200 Subject: [PATCH 09/44] FolderMemoryCard: Remember filter so it can be reapplied when memory cards are changed while game is running. --- pcsx2/gui/MemoryCardFolder.cpp | 3 ++- pcsx2/gui/MemoryCardFolder.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index ec7cc49ee..c1da43435 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -1025,7 +1025,7 @@ FolderMemoryCardAggregator::FolderMemoryCardAggregator() { void FolderMemoryCardAggregator::Open() { for ( int i = 0; i < totalCardSlots; ++i ) { - m_cards[i].Open(); + m_cards[i].Open( m_lastKnownFilter ); } } @@ -1071,5 +1071,6 @@ void FolderMemoryCardAggregator::ReIndex( uint slot, const wxString& filter ) { m_cards[slot].Close(); Console.WriteLn( Color_Green, L"(FolderMcd) Re-Indexing slot %u with filter \"%s\"", slot, WX_STR( filter ) ); m_cards[slot].Open( filter ); + m_lastKnownFilter = filter; } diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 8df9aff6c..1352d9566 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -298,6 +298,7 @@ class FolderMemoryCardAggregator { protected: static const int totalCardSlots = 8; FolderMemoryCard m_cards[totalCardSlots]; + wxString m_lastKnownFilter = L""; public: FolderMemoryCardAggregator(); From f32f42678c77782e51ffdef7ae6a938fefcd5db5 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 25 May 2015 05:08:02 +0200 Subject: [PATCH 10/44] FolderMemoryCard: Filter only system data by default. This reduces memory card initialization time. Without this, it always indexes all files when the emulator boots, which can take a few seconds if you have lots of files. With this, the only file indexed is the PS2 "your system configuration" file. This has the side-effect of not being able to see save files in the PS2 BIOS unless you insert a game disc matching the files you want to see. I don't think this is a big problem, but there should probably be a "don't filter" option somewhere in case you want to manage files in the BIOS. --- pcsx2/gui/MemoryCardFolder.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index c1da43435..d6505828c 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -298,6 +298,8 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS bool hasFilter = !filter.IsEmpty(); if ( hasFilter ) { localFilter = L"DATA-SYSTEM/" + filter; + } else { + localFilter = L"DATA-SYSTEM"; } int entryNumber = 2; // include . and .. @@ -319,7 +321,7 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS // if possible filter added directories by game serial // this has the effective result of only files relevant to the current game being loaded into the memory card // which means every game essentially sees the memory card as if no other files exist - if ( hasFilter && !FilterMatches( fileName, localFilter ) ) { + if ( !FilterMatches( fileName, localFilter ) ) { hasNext = dir.GetNext( &fileName ); continue; } From 72dcb9b94c1e24d53ab6163f9b22b8c274ddae6e Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 25 May 2015 05:15:44 +0200 Subject: [PATCH 11/44] MemoryCardListPanel: Minor visual bugfix. If you inserted a PSX memory card into a slot, then swapped that with a PS2 one, the memory card manager still displayed that card as a PSX one with "MBit" instead of "MiB" as the size unit. Fixed. --- pcsx2/gui/Panels/MemoryCardListPanel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/pcsx2/gui/Panels/MemoryCardListPanel.cpp b/pcsx2/gui/Panels/MemoryCardListPanel.cpp index ca6009277..db0ed221c 100644 --- a/pcsx2/gui/Panels/MemoryCardListPanel.cpp +++ b/pcsx2/gui/Panels/MemoryCardListPanel.cpp @@ -54,6 +54,7 @@ bool EnumerateMemoryCard( McdSlotItem& dest, const wxFileName& filename, const w { dest.IsFormatted = false; dest.IsPresent = false; + dest.IsPSX = false; dest.Type = MemoryCardType::MemoryCard_None; const wxString fullpath( filename.GetFullPath() ); From f40b6796537cd36ba6c648d5da9bb57f2b882ced Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 25 May 2015 14:39:35 +0200 Subject: [PATCH 12/44] Add option to enable/disable the filtering of the FolderMemoryCard. --- pcsx2/Config.h | 1 + pcsx2/Pcsx2Config.cpp | 2 ++ pcsx2/gui/Dialogs/McdConfigDialog.cpp | 9 +++++ pcsx2/gui/MemoryCardFile.cpp | 3 +- pcsx2/gui/MemoryCardFolder.cpp | 47 ++++++++++++++++----------- pcsx2/gui/MemoryCardFolder.h | 15 ++++++--- pcsx2/gui/Panels/MemoryCardPanels.h | 1 + 7 files changed, 53 insertions(+), 25 deletions(-) diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 270bb444c..d8561b0e5 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -441,6 +441,7 @@ struct Pcsx2Config BackupSavestate :1, // enables simulated ejection of memory cards when loading savestates McdEnableEjection :1, + McdFolderAutoManage :1, MultitapPort0_Enabled:1, MultitapPort1_Enabled:1, diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 33f80496c..a70cab4ca 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -398,6 +398,7 @@ Pcsx2Config::Pcsx2Config() bitset = 0; // Set defaults for fresh installs / reset settings McdEnableEjection = true; + McdFolderAutoManage = true; EnablePatches = true; BackupSavestate = true; } @@ -417,6 +418,7 @@ void Pcsx2Config::LoadSave( IniInterface& ini ) IniBitBool( BackupSavestate ); IniBitBool( McdEnableEjection ); + IniBitBool( McdFolderAutoManage ); IniBitBool( MultitapPort0_Enabled ); IniBitBool( MultitapPort1_Enabled ); diff --git a/pcsx2/gui/Dialogs/McdConfigDialog.cpp b/pcsx2/gui/Dialogs/McdConfigDialog.cpp index 20b958f70..cbe6a440f 100644 --- a/pcsx2/gui/Dialogs/McdConfigDialog.cpp +++ b/pcsx2/gui/Dialogs/McdConfigDialog.cpp @@ -40,6 +40,12 @@ Panels::McdConfigPanel_Toggles::McdConfigPanel_Toggles(wxWindow *parent) ) ); + m_folderAutoIndex = new pxCheckBox( this, + _( "Automatically manage saves based on running game" ), + pxE( L"(Folder type only) Re-index memory card content every time the running software changes. This prevents the memory card from running out of space for saves." + ) + ); + //m_check_SavestateBackup = new pxCheckBox( this, pxsFmt(_("Backup existing Savestate when creating a new one")) ); /* for( uint i=0; i<2; ++i ) @@ -64,6 +70,7 @@ Panels::McdConfigPanel_Toggles::McdConfigPanel_Toggles(wxWindow *parent) *this += new wxStaticLine( this ) | StdExpand(); *this += m_check_Ejection; + *this += m_folderAutoIndex; } void Panels::McdConfigPanel_Toggles::Apply() @@ -73,6 +80,7 @@ void Panels::McdConfigPanel_Toggles::Apply() //g_Conf->EmuOptions.BackupSavestate = m_check_SavestateBackup->GetValue(); g_Conf->EmuOptions.McdEnableEjection = m_check_Ejection->GetValue(); + g_Conf->EmuOptions.McdFolderAutoManage = m_folderAutoIndex->GetValue(); } void Panels::McdConfigPanel_Toggles::AppStatusEvent_OnSettingsApplied() @@ -82,6 +90,7 @@ void Panels::McdConfigPanel_Toggles::AppStatusEvent_OnSettingsApplied() //m_check_SavestateBackup ->SetValue( g_Conf->EmuOptions.BackupSavestate ); m_check_Ejection ->SetValue( g_Conf->EmuOptions.McdEnableEjection ); + m_folderAutoIndex ->SetValue( g_Conf->EmuOptions.McdFolderAutoManage ); } diff --git a/pcsx2/gui/MemoryCardFile.cpp b/pcsx2/gui/MemoryCardFile.cpp index fc7d89cd1..bafcf95db 100644 --- a/pcsx2/gui/MemoryCardFile.cpp +++ b/pcsx2/gui/MemoryCardFile.cpp @@ -436,6 +436,7 @@ uint FileMcd_ConvertToSlot( uint port, uint slot ) static void PS2E_CALLBACK FileMcd_EmuOpen( PS2E_THISPTR thisptr, const PS2E_SessionInfo *session ) { thisptr->impl.Open(); + thisptr->implFolder.SetFiltering( g_Conf->EmuOptions.McdFolderAutoManage ); thisptr->implFolder.Open(); } @@ -559,7 +560,7 @@ static void PS2E_CALLBACK FileMcd_ReIndex( PS2E_THISPTR thisptr, uint port, uint // thisptr->impl.ReIndex( combinedSlot, filter ); // break; case MemoryCardType::MemoryCard_Folder: - thisptr->implFolder.ReIndex( combinedSlot, filter ); + thisptr->implFolder.ReIndex( combinedSlot, g_Conf->EmuOptions.McdFolderAutoManage, filter ); break; default: return; diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index d6505828c..b20c7d14f 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -46,11 +46,7 @@ bool FolderMemoryCard::IsFormatted() { return m_superBlock.raw[0x16] == 0x6F; } -void FolderMemoryCard::Open() { - Open( L"" ); -} - -void FolderMemoryCard::Open( const wxString& filter ) { +void FolderMemoryCard::Open( const bool enableFiltering, const wxString& filter ) { InitializeInternalData(); wxFileName configuredFileName( g_Conf->FullpathToMcd( m_slot ) ); @@ -84,7 +80,7 @@ void FolderMemoryCard::Open( const wxString& filter ) { if ( disabled ) return; m_isEnabled = true; - LoadMemoryCardData( filter ); + LoadMemoryCardData( enableFiltering, filter ); SetTimeLastWrittenToNow(); m_framesUntilFlush = 0; @@ -102,7 +98,7 @@ void FolderMemoryCard::Close() { } } -void FolderMemoryCard::LoadMemoryCardData( const wxString& filter ) { +void FolderMemoryCard::LoadMemoryCardData( const bool enableFiltering, const wxString& filter ) { bool formatted = false; // read superblock if it exists @@ -116,10 +112,16 @@ void FolderMemoryCard::LoadMemoryCardData( const wxString& filter ) { // if superblock was valid, load folders and files if ( formatted ) { + if ( enableFiltering ) { + Console.WriteLn( Color_Green, L"(FolderMcd) Indexing slot %u with filter \"%s\".", m_slot, WX_STR( filter ) ); + } else { + Console.WriteLn( Color_Green, L"(FolderMcd) Indexing slot %u without filter.", m_slot ); + } + CreateFat(); CreateRootDir(); MemoryCardFileEntry* const rootDirEntry = &m_fileEntryDict[m_superBlock.data.rootdir_cluster].entries[0]; - AddFolder( rootDirEntry, folderName.GetPath(), filter ); + AddFolder( rootDirEntry, folderName.GetPath(), enableFiltering, filter ); } } @@ -284,7 +286,7 @@ bool FilterMatches( const wxString& fileName, const wxString& filter ) { return false; } -bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& filter ) { +bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const bool enableFiltering, const wxString& filter ) { wxDir dir( dirPath ); if ( dir.IsOpened() ) { Console.WriteLn( L"(FolderMcd) Adding folder: %s", WX_STR( dirPath ) ); @@ -295,11 +297,13 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS bool hasNext; wxString localFilter; - bool hasFilter = !filter.IsEmpty(); - if ( hasFilter ) { - localFilter = L"DATA-SYSTEM/" + filter; - } else { - localFilter = L"DATA-SYSTEM"; + if ( enableFiltering ) { + bool hasFilter = !filter.IsEmpty(); + if ( hasFilter ) { + localFilter = L"DATA-SYSTEM/" + filter; + } else { + localFilter = L"DATA-SYSTEM"; + } } int entryNumber = 2; // include . and .. @@ -321,7 +325,7 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS // if possible filter added directories by game serial // this has the effective result of only files relevant to the current game being loaded into the memory card // which means every game essentially sees the memory card as if no other files exist - if ( !FilterMatches( fileName, localFilter ) ) { + if ( enableFiltering && !FilterMatches( fileName, localFilter ) ) { hasNext = dir.GetNext( &fileName ); continue; } @@ -1027,7 +1031,7 @@ FolderMemoryCardAggregator::FolderMemoryCardAggregator() { void FolderMemoryCardAggregator::Open() { for ( int i = 0; i < totalCardSlots; ++i ) { - m_cards[i].Open( m_lastKnownFilter ); + m_cards[i].Open( m_enableFiltering, m_lastKnownFilter ); } } @@ -1037,6 +1041,10 @@ void FolderMemoryCardAggregator::Close() { } } +void FolderMemoryCardAggregator::SetFiltering( const bool enableFiltering ) { + m_enableFiltering = enableFiltering; +} + s32 FolderMemoryCardAggregator::IsPresent( uint slot ) { return m_cards[slot].IsPresent(); } @@ -1069,10 +1077,11 @@ void FolderMemoryCardAggregator::NextFrame( uint slot ) { m_cards[slot].NextFrame(); } -void FolderMemoryCardAggregator::ReIndex( uint slot, const wxString& filter ) { +void FolderMemoryCardAggregator::ReIndex( uint slot, const bool enableFiltering, const wxString& filter ) { m_cards[slot].Close(); - Console.WriteLn( Color_Green, L"(FolderMcd) Re-Indexing slot %u with filter \"%s\"", slot, WX_STR( filter ) ); - m_cards[slot].Open( filter ); + m_cards[slot].Open( enableFiltering, filter ); + + SetFiltering( enableFiltering ); m_lastKnownFilter = filter; } diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 1352d9566..94ea5e8c0 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -168,8 +168,7 @@ public: void Lock(); void Unlock(); - void Open(); - void Open( const wxString& filter ); + void Open( const bool enableFiltering, const wxString& filter ); void Close(); s32 IsPresent(); @@ -220,7 +219,9 @@ protected: // loads files and folders from the host file system if a superblock exists in the root directory - void LoadMemoryCardData( const wxString& filter ); + // if enableFiltering is set to true, only folders whose name contain the filter string are loaded + // filter string can include multiple filters by separating them with "/" + void LoadMemoryCardData( const bool enableFiltering, const wxString& filter ); // creates the FAT and indirect FAT void CreateFat(); @@ -253,7 +254,8 @@ protected: // adds a folder in the host file system to the memory card, including all files and subdirectories // - dirEntry: the entry of the directory in the parent directory, or the root "." entry // - dirPath: the full path to the directory in the host file system - bool AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& filter = L"" ); + // - enableFiltering and filter: filter loaded contents, see LoadMemoryCardData() + bool AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const bool enableFiltering = false, const wxString& filter = L"" ); // adds a file in the host file sytem to the memory card // - dirEntry: the entry of the directory in the parent directory, or the root "." entry @@ -298,6 +300,7 @@ class FolderMemoryCardAggregator { protected: static const int totalCardSlots = 8; FolderMemoryCard m_cards[totalCardSlots]; + bool m_enableFiltering = true; wxString m_lastKnownFilter = L""; public: @@ -307,6 +310,8 @@ public: void Open(); void Close(); + void SetFiltering( const bool enableFiltering ); + s32 IsPresent( uint slot ); void GetSizeInfo( uint slot, PS2E_McdSizeInfo& outways ); bool IsPSX( uint slot ); @@ -315,5 +320,5 @@ public: s32 EraseBlock( uint slot, u32 adr ); u64 GetCRC( uint slot ); void NextFrame( uint slot ); - void ReIndex( uint slot, const wxString& filter ); + void ReIndex( uint slot, const bool enableFiltering, const wxString& filter ); }; diff --git a/pcsx2/gui/Panels/MemoryCardPanels.h b/pcsx2/gui/Panels/MemoryCardPanels.h index d4daf113b..a58646c1a 100644 --- a/pcsx2/gui/Panels/MemoryCardPanels.h +++ b/pcsx2/gui/Panels/MemoryCardPanels.h @@ -285,6 +285,7 @@ namespace Panels protected: //pxCheckBox* m_check_Multitap[2]; pxCheckBox* m_check_Ejection; + pxCheckBox* m_folderAutoIndex; //moved to the system menu, just below "Save State" //pxCheckBox* m_check_SavestateBackup; From 8029412fb7ff64b969ce1f5020cacd5fca6f495c Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 30 May 2015 00:42:05 +0200 Subject: [PATCH 13/44] FolderMemoryCard: Some code cleanup. --- pcsx2/gui/MemoryCardFile.cpp | 13 ++---- pcsx2/gui/MemoryCardFolder.cpp | 68 ++++++++++----------------- pcsx2/gui/MemoryCardFolder.h | 84 +++++++++++++++++++++++----------- 3 files changed, 86 insertions(+), 79 deletions(-) diff --git a/pcsx2/gui/MemoryCardFile.cpp b/pcsx2/gui/MemoryCardFile.cpp index bafcf95db..5463334cc 100644 --- a/pcsx2/gui/MemoryCardFile.cpp +++ b/pcsx2/gui/MemoryCardFile.cpp @@ -75,8 +75,6 @@ public: s32 EraseBlock ( uint slot, u32 adr ); u64 GetCRC ( uint slot ); - void NextFrame( uint slot ); - protected: bool Seek( wxFFile& f, u32 adr ); bool Create( const wxString& mcdFile, uint sizeInMB ); @@ -408,11 +406,6 @@ u64 FileMemoryCard::GetCRC( uint slot ) return retval; } -void FileMemoryCard::NextFrame( uint slot ) -{ - -} - // -------------------------------------------------------------------------------------- // MemoryCard Component API Bindings // -------------------------------------------------------------------------------------- @@ -542,9 +535,9 @@ static u64 PS2E_CALLBACK FileMcd_GetCRC( PS2E_THISPTR thisptr, uint port, uint s static void PS2E_CALLBACK FileMcd_NextFrame( PS2E_THISPTR thisptr, uint port, uint slot ) { const uint combinedSlot = FileMcd_ConvertToSlot( port, slot ); switch ( g_Conf->Mcd[combinedSlot].Type ) { - case MemoryCardType::MemoryCard_File: - thisptr->impl.NextFrame( combinedSlot ); - break; + //case MemoryCardType::MemoryCard_File: + // thisptr->impl.NextFrame( combinedSlot ); + // break; case MemoryCardType::MemoryCard_Folder: thisptr->implFolder.NextFrame( combinedSlot ); break; diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index b20c7d14f..cc3824fe1 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -1,5 +1,5 @@ /* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 PCSX2 Dev Team + * Copyright (C) 2002-2015 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- @@ -47,14 +47,18 @@ bool FolderMemoryCard::IsFormatted() { } void FolderMemoryCard::Open( const bool enableFiltering, const wxString& filter ) { + Open( g_Conf->FullpathToMcd( m_slot ), g_Conf->Mcd[m_slot], enableFiltering, filter ); +} + +void FolderMemoryCard::Open( const wxString& fullPath, const AppConfig::McdOptions& mcdOptions, const bool enableFiltering, const wxString& filter ) { InitializeInternalData(); - wxFileName configuredFileName( g_Conf->FullpathToMcd( m_slot ) ); - folderName = wxFileName( configuredFileName.GetFullPath() + L"/" ); + wxFileName configuredFileName( fullPath ); + m_folderName = wxFileName( configuredFileName.GetFullPath() + L"/" ); wxString str( configuredFileName.GetFullPath() ); bool disabled = false; - if ( g_Conf->Mcd[m_slot].Enabled && g_Conf->Mcd[m_slot].Type == MemoryCardType::MemoryCard_Folder ) { + if ( mcdOptions.Enabled && mcdOptions.Type == MemoryCardType::MemoryCard_Folder ) { if ( configuredFileName.GetFullName().IsEmpty() ) { str = L"[empty filename]"; disabled = true; @@ -65,8 +69,8 @@ void FolderMemoryCard::Open( const bool enableFiltering, const wxString& filter } // if nothing exists at a valid location, create a directory for the memory card - if ( !disabled && !folderName.DirExists() ) { - if ( !folderName.Mkdir() ) { + if ( !disabled && !m_folderName.DirExists() ) { + if ( !m_folderName.Mkdir() ) { str = L"[couldn't create folder]"; disabled = true; } @@ -91,7 +95,7 @@ void FolderMemoryCard::Close() { Flush(); - wxFileName superBlockFileName( folderName.GetPath(), L"_pcsx2_superblock" ); + wxFileName superBlockFileName( m_folderName.GetPath(), L"_pcsx2_superblock" ); wxFFile superBlockFile( superBlockFileName.GetFullPath().c_str(), L"wb" ); if ( superBlockFile.IsOpened() ) { superBlockFile.Write( &m_superBlock.raw, sizeof( m_superBlock.raw ) ); @@ -102,7 +106,7 @@ void FolderMemoryCard::LoadMemoryCardData( const bool enableFiltering, const wxS bool formatted = false; // read superblock if it exists - wxFileName superBlockFileName( folderName.GetPath(), L"_pcsx2_superblock" ); + wxFileName superBlockFileName( m_folderName.GetPath(), L"_pcsx2_superblock" ); if ( superBlockFileName.FileExists() ) { wxFFile superBlockFile( superBlockFileName.GetFullPath().c_str(), L"rb" ); if ( superBlockFile.IsOpened() && superBlockFile.Read( &m_superBlock.raw, sizeof( m_superBlock.raw ) ) >= sizeof( m_superBlock.data ) ) { @@ -121,7 +125,7 @@ void FolderMemoryCard::LoadMemoryCardData( const bool enableFiltering, const wxS CreateFat(); CreateRootDir(); MemoryCardFileEntry* const rootDirEntry = &m_fileEntryDict[m_superBlock.data.rootdir_cluster].entries[0]; - AddFolder( rootDirEntry, folderName.GetPath(), enableFiltering, filter ); + AddFolder( rootDirEntry, m_folderName.GetPath(), enableFiltering, filter ); } } @@ -224,29 +228,6 @@ u32 FolderMemoryCard::GetLastClusterOfData( const u32 cluster ) { return entryCluster; } -u64 FolderMemoryCard::ConvertToMemoryCardTimestamp( const wxDateTime& time ) { - if ( !time.IsValid() ) { - return 0; - } - - union { - MemoryCardFileEntryDateTime data; - u64 value; - } t; - - wxDateTime::Tm tm = time.GetTm( wxDateTime::GMT9 ); - - t.data.unused = 0; - t.data.second = tm.sec; - t.data.minute = tm.min; - t.data.hour = tm.hour; - t.data.day = tm.mday; - t.data.month = tm.mon + 1; - t.data.year = tm.year; - - return t.value; -} - MemoryCardFileEntry* FolderMemoryCard::AppendFileEntryToDir( MemoryCardFileEntry* const dirEntry ) { u32 entryCluster = GetLastClusterOfData( dirEntry->entry.data.cluster ); @@ -358,8 +339,8 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS metaFile.Close(); } else { newDirEntry->entry.data.mode = MemoryCardFileEntry::DefaultDirMode; - newDirEntry->entry.data.timeCreated.value = ConvertToMemoryCardTimestamp( creationTime ); - newDirEntry->entry.data.timeModified.value = ConvertToMemoryCardTimestamp( modificationTime ); + newDirEntry->entry.data.timeCreated = MemoryCardFileEntryDateTime::FromWxDateTime( creationTime ); + newDirEntry->entry.data.timeModified = MemoryCardFileEntryDateTime::FromWxDateTime( modificationTime ); } newDirEntry->entry.data.length = 2; @@ -398,7 +379,7 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName ) { wxFileName relativeFilePath( dirPath, fileName ); - relativeFilePath.MakeRelativeTo( folderName.GetPath() ); + relativeFilePath.MakeRelativeTo( m_folderName.GetPath() ); Console.WriteLn( L"(FolderMcd) Adding file: %s", WX_STR( relativeFilePath.GetFullPath() ) ); wxFileName fileInfo( dirPath, fileName ); @@ -430,8 +411,8 @@ bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxStr metaFile.Close(); } else { newFileEntry->entry.data.mode = MemoryCardFileEntry::DefaultFileMode; - newFileEntry->entry.data.timeCreated.value = ConvertToMemoryCardTimestamp( creationTime ); - newFileEntry->entry.data.timeModified.value = ConvertToMemoryCardTimestamp( modificationTime ); + newFileEntry->entry.data.timeCreated = MemoryCardFileEntryDateTime::FromWxDateTime( creationTime ); + newFileEntry->entry.data.timeModified = MemoryCardFileEntryDateTime::FromWxDateTime( modificationTime ); } newFileEntry->entry.data.length = filesize; @@ -601,7 +582,7 @@ bool FolderMemoryCard::ReadFromFile( u8 *dest, u32 adr, u32 dataLength ) { const u32 fatCluster = cluster - m_superBlock.data.alloc_offset; // figure out which file to read from - wxFileName fileName( folderName ); + wxFileName fileName( m_folderName ); u32 clusterNumber; const MemoryCardFileEntry* const entry = GetFileEntryFromFileDataCluster( m_superBlock.data.rootdir_cluster, fatCluster, &fileName, fileName.GetDirCount(), &clusterNumber ); if ( entry != nullptr ) { @@ -768,6 +749,7 @@ void FolderMemoryCard::Flush() { const u32 rootDirCluster = m_superBlock.data.rootdir_cluster; const u32 rootDirPage = ( rootDirCluster + m_superBlock.data.alloc_offset ) * 2; Flush( rootDirPage ); + Flush( rootDirPage + 1 ); MemoryCardFileEntryCluster* rootEntries = &m_fileEntryDict[rootDirCluster]; if ( rootEntries->entries[0].IsValid() && rootEntries->entries[0].IsUsed() ) { FlushFileEntries( rootDirCluster, rootEntries->entries[0].entry.data.length ); @@ -807,7 +789,7 @@ void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remaini const wxString subDirPath = dirPath + L"/" + subDirName; // if this directory has nonstandard metadata, write that to the file system - wxFileName metaFileName( folderName.GetFullPath() + subDirPath + L"/_pcsx2_meta_directory" ); + wxFileName metaFileName( m_folderName.GetFullPath() + subDirPath + L"/_pcsx2_meta_directory" ); if ( entry->entry.data.mode != MemoryCardFileEntry::DefaultDirMode || entry->entry.data.attr != 0 ) { if ( !metaFileName.DirExists() ) { metaFileName.Mkdir(); @@ -879,7 +861,7 @@ bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { const u32 fatCluster = cluster - m_superBlock.data.alloc_offset; // figure out which file to write to - wxFileName fileName( folderName ); + wxFileName fileName( m_folderName ); u32 clusterNumber; const MemoryCardFileEntry* const entry = GetFileEntryFromFileDataCluster( m_superBlock.data.rootdir_cluster, fatCluster, &fileName, fileName.GetDirCount(), &clusterNumber ); if ( entry != nullptr ) { @@ -1024,19 +1006,19 @@ void FolderMemoryCard::CalculateECC( u8* ecc, const u8* data ) { } FolderMemoryCardAggregator::FolderMemoryCardAggregator() { - for ( uint i = 0; i < totalCardSlots; ++i ) { + for ( uint i = 0; i < TotalCardSlots; ++i ) { m_cards[i].SetSlot( i ); } } void FolderMemoryCardAggregator::Open() { - for ( int i = 0; i < totalCardSlots; ++i ) { + for ( int i = 0; i < TotalCardSlots; ++i ) { m_cards[i].Open( m_enableFiltering, m_lastKnownFilter ); } } void FolderMemoryCardAggregator::Close() { - for ( int i = 0; i < totalCardSlots; ++i ) { + for ( int i = 0; i < TotalCardSlots; ++i ) { m_cards[i].Close(); } } diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 94ea5e8c0..9d4a9b881 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -1,5 +1,5 @@ /* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 PCSX2 Dev Team + * Copyright (C) 2002-2015 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- @@ -17,11 +17,11 @@ #include #include -#include #include #include #include "PluginCallbacks.h" +#include "AppConfig.h" // -------------------------------------------------------------------------------------- // Superblock Header Struct @@ -57,6 +57,32 @@ struct MemoryCardFileEntryDateTime { u8 day; u8 month; u16 year; + + static MemoryCardFileEntryDateTime FromWxDateTime( const wxDateTime& time ) { + MemoryCardFileEntryDateTime t; + + if ( time.IsValid() ) { + wxDateTime::Tm tm = time.GetTm( wxDateTime::GMT9 ); + + t.unused = 0; + t.second = tm.sec; + t.minute = tm.min; + t.hour = tm.hour; + t.day = tm.mday; + t.month = tm.mon + 1; + t.year = tm.year; + } else { + t.unused = 0; + t.second = 0; + t.minute = 0; + t.hour = 0; + t.day = 0; + t.month = 0; + t.year = 0; + } + + return t; + } }; #pragma pack(pop) @@ -70,18 +96,10 @@ struct MemoryCardFileEntry { struct MemoryCardFileEntryData { u32 mode; u32 length; // number of bytes for file, number of files for dir - union { - MemoryCardFileEntryDateTime data; - u64 value; - u8 raw[8]; - } timeCreated; + MemoryCardFileEntryDateTime timeCreated; u32 cluster; // cluster the start of referred file or folder can be found in u32 dirEntry; // parent directory entry number, only used if "." entry of subdir - union { - MemoryCardFileEntryDateTime data; - u64 value; - u8 raw[8]; - } timeModified; + MemoryCardFileEntryDateTime timeModified; u32 attr; u8 padding[0x1C]; u8 name[0x20]; @@ -118,9 +136,7 @@ struct MemoryCardPage { // -------------------------------------------------------------------------------------- // Fakes a memory card using a regular folder/file structure in the host file system class FolderMemoryCard { -protected: - wxFileName folderName; - +public: // a few constants so we could in theory change the memory card size without too much effort static const int IndirectFatClusterCount = 1; // should be 32 but only 1 is ever used static const int PageSize = MemoryCardPage::PageSize; @@ -133,9 +149,11 @@ protected: static const int TotalPages = 0x4000; static const int TotalClusters = TotalPages / 2; static const int TotalBlocks = TotalClusters / 8; + static const int TotalSizeRaw = TotalPages * PageSizeRaw; static const int FramesAfterWriteUntilFlush = 60; +protected: union superBlockUnion { superblock data; u8 raw[BlockSize]; @@ -151,15 +169,24 @@ protected: u8 m_backupBlock1[BlockSize]; u8 m_backupBlock2[BlockSize]; + // stores directory and file metadata std::map m_fileEntryDict; - // holds a copy of modified areas of the memory card, in page-sized chunks + // holds a copy of modified pages of the memory card before they're flushed to the file system std::map m_cache; - - uint m_slot; - bool m_isEnabled; - u64 m_timeLastWritten; + // if > 0, the amount of frames until data is flushed to the file system + // reset to FramesAfterWriteUntilFlush on each write int m_framesUntilFlush; + // used to figure out if contents were changed for savestate-related purposes, see GetCRC() + u64 m_timeLastWritten; + + // path to the folder that contains the files of this memory card + wxFileName m_folderName; + + // PS2 memory card slot this card is inserted into + uint m_slot; + + bool m_isEnabled; public: FolderMemoryCard(); @@ -168,7 +195,10 @@ public: void Lock(); void Unlock(); + // Initialize & Load Memory Card with values configured in the Memory Card Manager void Open( const bool enableFiltering, const wxString& filter ); + // Initialize & Load Memory Card with provided custom values + void Open( const wxString& fullPath, const AppConfig::McdOptions& mcdOptions, const bool enableFiltering, const wxString& filter ); void Close(); s32 IsPresent(); @@ -219,8 +249,8 @@ protected: // loads files and folders from the host file system if a superblock exists in the root directory - // if enableFiltering is set to true, only folders whose name contain the filter string are loaded - // filter string can include multiple filters by separating them with "/" + // - enableFiltering: if set to true, only folders whose name contain the filter string are loaded + // - filter: can include multiple filters by separating them with "/" void LoadMemoryCardData( const bool enableFiltering, const wxString& filter ); // creates the FAT and indirect FAT @@ -244,8 +274,6 @@ protected: // returns the final cluster of the file or directory which is (partially) stored in the given cluster u32 GetLastClusterOfData( const u32 cluster ); - u64 ConvertToMemoryCardTimestamp( const wxDateTime& time ); - // creates and returns a new file entry in the given directory entry, ready to be filled // returns nullptr when the memory card is full @@ -282,6 +310,7 @@ protected: void SetTimeLastWrittenToNow(); + wxString GetDisabledMessage( uint slot ) const { return wxsFormat( pxE( L"The PS2-slot %d has been automatically disabled. You can correct the problem\nand re-enable it at any time using Config:Memory cards from the main menu." ), slot//TODO: translate internal slot index to human-readable slot description @@ -298,8 +327,11 @@ protected: // Forwards the API's requests for specific memory card slots to the correct FolderMemoryCard. class FolderMemoryCardAggregator { protected: - static const int totalCardSlots = 8; - FolderMemoryCard m_cards[totalCardSlots]; + static const int TotalCardSlots = 8; + FolderMemoryCard m_cards[TotalCardSlots]; + + // stores the specifics of the current filtering settings, so they can be + // re-applied automatically when memory cards are reloaded bool m_enableFiltering = true; wxString m_lastKnownFilter = L""; From d19facfb4369cf83e0fd6f9fb2aa16ced454e215 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sun, 31 May 2015 00:43:15 +0200 Subject: [PATCH 14/44] FolderMemoryCard: Add abililty to (re)set simulated memory card size. This will probably only be used to reset a converted card back to 8MB. Actually using a card as over 8MB is completely untested. --- pcsx2/gui/MemoryCardFolder.cpp | 38 ++++++++++++++++++++++++++++------ pcsx2/gui/MemoryCardFolder.h | 8 +++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index cc3824fe1..4effb1570 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -449,7 +449,7 @@ s32 FolderMemoryCard::IsPresent() { void FolderMemoryCard::GetSizeInfo( PS2E_McdSizeInfo& outways ) { outways.SectorSize = PageSize; outways.EraseBlockSizeInSectors = BlockSize / PageSize; - outways.McdSizeInSectors = TotalPages; + outways.McdSizeInSectors = GetSizeInClusters() * 2; u8 *pdata = (u8*)&outways.McdSizeInSectors; outways.Xor = 18; @@ -720,13 +720,15 @@ void FolderMemoryCard::Flush() { // first write the superblock if necessary Flush( 0 ); - if ( !IsFormatted() ) { return; } + const u32 clusterCount = GetSizeInClusters(); + const u32 pageCount = clusterCount * 2; + // then write the indirect FAT for ( int i = 0; i < IndirectFatClusterCount; ++i ) { const u32 cluster = m_superBlock.data.ifc_list[i]; - if ( cluster > 0 && cluster < TotalClusters ) { + if ( cluster > 0 && cluster < clusterCount ) { const u32 page = cluster * 2; Flush( page ); Flush( page + 1 ); @@ -737,7 +739,7 @@ void FolderMemoryCard::Flush() { for ( int i = 0; i < IndirectFatClusterCount; ++i ) { for ( int j = 0; j < ClusterSize / 4; ++j ) { const u32 cluster = m_indirectFat.data[i][j]; - if ( cluster > 0 && cluster < TotalClusters ) { + if ( cluster > 0 && cluster < clusterCount ) { const u32 page = cluster * 2; Flush( page ); Flush( page + 1 ); @@ -756,14 +758,13 @@ void FolderMemoryCard::Flush() { } // and finally, flush everything that hasn't been flushed yet - for ( int i = 0; i < TotalPages; ++i ) { + for ( uint i = 0; i < pageCount; ++i ) { Flush( i ); } } void FolderMemoryCard::Flush( const u32 page ) { - if ( page >= TotalPages ) { return; } auto it = m_cache.find( page ); if ( it != m_cache.end() ) { WriteWithoutCache( &it->second.raw[0], page * PageSizeRaw, PageSize ); @@ -954,6 +955,31 @@ void FolderMemoryCard::SetSlot( uint slot ) { m_slot = slot; } +u32 FolderMemoryCard::GetSizeInClusters() { + const u32 clusters = m_superBlock.data.clusters_per_card; + if ( clusters > 0 && clusters < UINT32_MAX ) { + return clusters; + } else { + return TotalClusters; + } +} + +void FolderMemoryCard::SetSizeInClusters( u32 clusters ) { + m_superBlock.data.clusters_per_card = clusters; + + const u32 alloc_offset = clusters / 0x100 + 9; + m_superBlock.data.alloc_offset = alloc_offset; + m_superBlock.data.alloc_end = clusters - 0x10 - alloc_offset; + + const u32 blocks = clusters / 8; + m_superBlock.data.backup_block1 = blocks - 1; + m_superBlock.data.backup_block2 = blocks - 2; +} + +void FolderMemoryCard::SetSizeInMB( u32 megaBytes ) { + SetSizeInClusters( ( megaBytes * 1024 * 1024 ) / ClusterSize ); +} + void FolderMemoryCard::SetTimeLastWrittenToNow() { m_timeLastWritten = wxGetLocalTimeMillis().GetValue(); m_framesUntilFlush = FramesAfterWriteUntilFlush; diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 9d4a9b881..a9c42df13 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -211,6 +211,14 @@ public: void SetSlot( uint slot ); + u32 GetSizeInClusters(); + + // WARNING: The intended use-case for this is resetting back to 8MB if a differently-sized superblock was loaded + // setting to a different size is untested and will probably not work correctly + void SetSizeInClusters( u32 clusters ); + // see SetSizeInClusters() + void SetSizeInMB( u32 megaBytes ); + // called once per frame, used for flushing data after FramesAfterWriteUntilFlush frames of no writes void NextFrame(); From 7e194f1a2628066a41b52a0484813d9fc8c3a783 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 30 May 2015 14:09:47 +0200 Subject: [PATCH 15/44] MemoryCard: Add option to convert a memory card to another type in the Memory Card Manager. --- pcsx2/CMakeLists.txt | 1 + pcsx2/gui/Dialogs/ConfigurationDialog.h | 22 ++ pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp | 197 ++++++++++++++++++ pcsx2/gui/Panels/MemoryCardListPanel.cpp | 47 +++++ pcsx2/gui/Panels/MemoryCardPanels.h | 4 + pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj | 1 + .../VCprojects/pcsx2_vs2012.vcxproj.filters | 3 + pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj | 1 + .../VCprojects/pcsx2_vs2013.vcxproj.filters | 3 + 9 files changed, 279 insertions(+) create mode 100644 pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 9a958ce20..17d1b1c40 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -272,6 +272,7 @@ set(pcsx2GuiSources gui/Panels/ThemeSelectorPanel.cpp gui/Dialogs/BaseConfigurationDialog.cpp gui/Dialogs/ConfirmationDialogs.cpp + gui/Dialogs/ConvertMemoryCardDialog.cpp gui/Dialogs/CreateMemoryCardDialog.cpp gui/Dialogs/FirstTimeWizard.cpp gui/Dialogs/GameDatabaseDialog.cpp diff --git a/pcsx2/gui/Dialogs/ConfigurationDialog.h b/pcsx2/gui/Dialogs/ConfigurationDialog.h index 8dbe07a88..faaf3cc30 100644 --- a/pcsx2/gui/Dialogs/ConfigurationDialog.h +++ b/pcsx2/gui/Dialogs/ConfigurationDialog.h @@ -237,4 +237,26 @@ namespace Dialogs void CreateControls(); void OnOk_Click( wxCommandEvent& evt ); }; + + // -------------------------------------------------------------------------------------- + // ConvertMemoryCardDialog + // -------------------------------------------------------------------------------------- + class ConvertMemoryCardDialog : public wxDialogWithHelpers + { + protected: + wxDirName m_mcdPath; + wxString m_mcdSourceFilename; + wxTextCtrl* m_text_filenameInput; + pxRadioPanel* m_radio_CardType; + + public: + virtual ~ConvertMemoryCardDialog() throw() {} + ConvertMemoryCardDialog( wxWindow* parent, const wxDirName& mcdPath, const AppConfig::McdOptions& mcdSourceConfig ); + + protected: + void CreateControls( const MemoryCardType sourceType ); + void OnOk_Click( wxCommandEvent& evt ); + bool ConvertToFile( const wxFileName& sourcePath, const wxFileName& targetPath ); + bool ConvertToFolder( const wxFileName& sourcePath, const wxFileName& targetPath ); + }; } diff --git a/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp b/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp new file mode 100644 index 000000000..9c2766794 --- /dev/null +++ b/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp @@ -0,0 +1,197 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2015 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" +#include "ConfigurationDialog.h" +#include "System.h" + +#include "MemoryCardFile.h" +#include "MemoryCardFolder.h" +#include + +Dialogs::ConvertMemoryCardDialog::ConvertMemoryCardDialog( wxWindow* parent, const wxDirName& mcdPath, const AppConfig::McdOptions& mcdSourceConfig ) + : wxDialogWithHelpers( parent, _( "Convert a memory card to a different format" ) ) + , m_mcdPath( mcdPath ) + , m_mcdSourceFilename( mcdSourceConfig.Filename.GetFullName() ) +{ + SetMinWidth( 472 ); + + CreateControls( mcdSourceConfig.Type ); + + if ( m_radio_CardType ) m_radio_CardType->Realize(); + + wxBoxSizer& s_buttons( *new wxBoxSizer( wxHORIZONTAL ) ); + s_buttons += new wxButton( this, wxID_OK, _( "Convert" ) ) | pxProportion( 2 ); + s_buttons += pxStretchSpacer( 3 ); + s_buttons += new wxButton( this, wxID_CANCEL ) | pxProportion( 2 ); + + wxBoxSizer& s_padding( *new wxBoxSizer( wxVERTICAL ) ); + + s_padding += Heading( wxString( _( "Convert: " ) ) + ( mcdPath + m_mcdSourceFilename ).GetFullPath() ).Unwrapped() | pxSizerFlags::StdExpand(); + + wxBoxSizer& s_filename( *new wxBoxSizer( wxHORIZONTAL ) ); + s_filename += Heading( _( "To: " ) ).SetMinWidth( 50 ); + m_text_filenameInput->SetMinSize( wxSize( 250, 20 ) ); + m_text_filenameInput->SetValue( wxFileName( m_mcdSourceFilename ).GetName() + L"_converted" ); + s_filename += m_text_filenameInput; + s_filename += Heading( L".ps2" ); + + s_padding += s_filename | wxALIGN_LEFT; + + s_padding += m_radio_CardType | pxSizerFlags::StdExpand(); + + s_padding += Heading( pxE( L"WARNING: Converting a memory card may take several minutes! Please do not close the emulator during the conversion process, even if the emulator is no longer responding to input." ) ); + + s_padding += 12; + s_padding += s_buttons | pxSizerFlags::StdCenter(); + + *this += s_padding | pxSizerFlags::StdExpand(); + + Connect( wxID_OK, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConvertMemoryCardDialog::OnOk_Click ) ); + Connect( m_text_filenameInput->GetId(), wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( ConvertMemoryCardDialog::OnOk_Click ) ); + + m_text_filenameInput->SetFocus(); + m_text_filenameInput->SelectAll(); +} + +void Dialogs::ConvertMemoryCardDialog::CreateControls( const MemoryCardType sourceType ) { + m_text_filenameInput = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER ); + + RadioPanelItem toFile = RadioPanelItem( _( "File" ), pxE( L"Convert this memory card to a regular 8 MB .ps2 file. Please note that the resulting file may not actually contain all saves, depending on how many are in the source memory card." ) ) + .SetInt( MemoryCardType::MemoryCard_File ); + RadioPanelItem toFolder = RadioPanelItem( _( "Folder" ), _( "Convert this memory card to a folder of individual saves." ) ) + .SetInt( MemoryCardType::MemoryCard_Folder ); + + const RadioPanelItem tblForFile[] = { toFolder }; + const RadioPanelItem tblForFolder[] = { toFile }; + + switch ( sourceType ) { + case MemoryCardType::MemoryCard_File: + m_radio_CardType = new pxRadioPanel( this, tblForFile ); + break; + case MemoryCardType::MemoryCard_Folder: + m_radio_CardType = new pxRadioPanel( this, tblForFolder ); + break; + default: + Console.Error( L"Memory Card Conversion: Invalid source type!" ); + return; + } + + m_radio_CardType->SetDefaultItem( 0 ); +} + +void Dialogs::ConvertMemoryCardDialog::OnOk_Click( wxCommandEvent& evt ) { + wxString composedName = m_text_filenameInput->GetValue().Trim() + L".ps2"; + + wxString errMsg; + if ( !isValidNewFilename( composedName, m_mcdPath, errMsg, 5 ) ) { + wxString message; + message.Printf( _( "Error (%s)" ), errMsg.c_str() ); + Msgbox::Alert( message, _( "Convert memory card" ) ); + m_text_filenameInput->SetFocus(); + m_text_filenameInput->SelectAll(); + return; + } + + bool success = false; + + wxFileName sourcePath = ( m_mcdPath + m_mcdSourceFilename ); + wxFileName targetPath = ( m_mcdPath + composedName ); + if ( m_radio_CardType ) { + MemoryCardType targetType = (MemoryCardType)m_radio_CardType->SelectedItem().SomeInt; + + switch ( targetType ) { + case MemoryCardType::MemoryCard_File: + success = ConvertToFile( sourcePath, targetPath ); + break; + case MemoryCardType::MemoryCard_Folder: + success = ConvertToFolder( sourcePath, targetPath ); + break; + } + } + + if ( !success ) { + Msgbox::Alert( _( "Memory Card conversion failed for unknown reasons." ), _( "Convert memory card" ) ); + return; + } + + EndModal( wxID_OK ); +} + +bool Dialogs::ConvertMemoryCardDialog::ConvertToFile( const wxFileName& sourcePath, const wxFileName& targetPath ) { + // Conversion method: Open FolderMcd as usual, then read the raw data from it and write it to a file stream + + wxFFile targetFile( targetPath.GetFullPath(), L"wb" ); + if ( !targetFile.IsOpened() ) { + return false; + } + + FolderMemoryCard sourceFolderMemoryCard; + AppConfig::McdOptions config; + config.Enabled = true; + config.Type = MemoryCardType::MemoryCard_Folder; + sourceFolderMemoryCard.Open( sourcePath.GetFullPath(), config, false, L"" ); + + u8 buffer[FolderMemoryCard::PageSizeRaw]; + u32 adr = 0; + while ( adr < FolderMemoryCard::TotalSizeRaw ) { + sourceFolderMemoryCard.Read( buffer, adr, FolderMemoryCard::PageSizeRaw ); + targetFile.Write( buffer, FolderMemoryCard::PageSizeRaw ); + adr += FolderMemoryCard::PageSizeRaw; + } + + targetFile.Close(); + sourceFolderMemoryCard.Close(); + + return true; +} + +bool Dialogs::ConvertMemoryCardDialog::ConvertToFolder( const wxFileName& sourcePath, const wxFileName& targetPath ) { + // Conversion method: Read all pages of the FileMcd into a FolderMcd, then just write that out with the regular methods + // TODO: Test if >8MB files don't super fuck up something + + wxFFile sourceFile( sourcePath.GetFullPath(), L"rb" ); + if ( !sourceFile.IsOpened() ) { + return false; + } + + u8 buffer[FolderMemoryCard::PageSizeRaw]; + FolderMemoryCard targetFolderMemoryCard; + AppConfig::McdOptions config; + config.Enabled = true; + config.Type = MemoryCardType::MemoryCard_Folder; + targetFolderMemoryCard.Open( targetPath.GetFullPath(), config, false, L"" ); + + u32 adr = 0; + while ( !sourceFile.Eof() ) { + int size = sourceFile.Read( buffer, FolderMemoryCard::PageSizeRaw ); + if ( size > 0 ) { + targetFolderMemoryCard.Save( buffer, adr, size ); + adr += size; + } + } + + sourceFile.Close(); + targetFolderMemoryCard.Close(); + + if ( adr != FolderMemoryCard::TotalSizeRaw ) { + // reset memory card metrics in superblock to the default 8MB, since the converted card was different + targetFolderMemoryCard.Open( targetPath.GetFullPath(), config, true, L"" ); + targetFolderMemoryCard.SetSizeInMB( 8 ); + targetFolderMemoryCard.Close(); + } + + return true; +} diff --git a/pcsx2/gui/Panels/MemoryCardListPanel.cpp b/pcsx2/gui/Panels/MemoryCardListPanel.cpp index db0ed221c..d391088bc 100644 --- a/pcsx2/gui/Panels/MemoryCardListPanel.cpp +++ b/pcsx2/gui/Panels/MemoryCardListPanel.cpp @@ -466,6 +466,7 @@ enum McdMenuId McdMenuId_RefreshList, McdMenuId_AssignUnassign, McdMenuId_Duplicate, + McdMenuId_Convert, }; @@ -492,6 +493,7 @@ Panels::MemoryCardListPanel_Simple::MemoryCardListPanel_Simple( wxWindow* parent m_button_Duplicate = new wxButton(this, wxID_ANY, _("Duplicate ...")); m_button_Rename = new wxButton(this, wxID_ANY, _("Rename ...")); m_button_Create = new wxButton(this, wxID_ANY, _("Create ...")); + m_button_Convert = new wxButton(this, wxID_ANY, _("Convert ...")); // ------------------------------------ // Sizer / Layout Section @@ -511,6 +513,8 @@ Panels::MemoryCardListPanel_Simple::MemoryCardListPanel_Simple( wxWindow* parent *s_leftside_buttons += m_button_Rename; *s_leftside_buttons += 2; *s_leftside_buttons += m_button_Create; + *s_leftside_buttons += 2; + *s_leftside_buttons += m_button_Convert; SetSizerAndFit(GetSizer()); parent->SetWindowStyle(parent->GetWindowStyle() | wxRESIZE_BORDER); @@ -526,12 +530,14 @@ Panels::MemoryCardListPanel_Simple::MemoryCardListPanel_Simple( wxWindow* parent // Connect( m_button_Mount->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnMountCard)); Connect( m_button_Create->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnCreateOrDeleteCard)); + Connect( m_button_Convert->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnConvertCard)); Connect( m_button_Rename->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnRenameFile)); Connect( m_button_Duplicate->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnDuplicateFile)); Connect( m_button_AssignUnassign->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnAssignUnassignFile)); // Popup Menu Connections! Connect( McdMenuId_Create, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnCreateOrDeleteCard) ); + Connect( McdMenuId_Convert, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnConvertCard) ); //Connect( McdMenuId_Mount, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnMountCard) ); Connect( McdMenuId_Rename, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnRenameFile) ); Connect( McdMenuId_AssignUnassign, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnAssignUnassignFile) ); @@ -562,6 +568,7 @@ void Panels::MemoryCardListPanel_Simple::UpdateUI() m_button_Rename->Disable(); m_button_Duplicate->Disable(); m_button_AssignUnassign->Disable(); + m_button_Convert->Disable(); return; } @@ -581,6 +588,8 @@ void Panels::MemoryCardListPanel_Simple::UpdateUI() wxString dupTip = _("Create a duplicate of this memory card ..."); pxSetToolTip( m_button_Duplicate, dupTip ); + m_button_Convert->Enable( card.IsPresent && card.IsFormatted && !card.IsPSX ); + //m_button_Create->Enable( card.Slot>=0 || card.IsPresent); m_button_Create->SetLabel( card.IsPresent ? _("Delete") : _("Create ...") ); @@ -747,6 +756,29 @@ void Panels::MemoryCardListPanel_Simple::UiCreateNewCard( McdSlotItem& card ) closed_core.AllowResume(); } +void Panels::MemoryCardListPanel_Simple::UiConvertCard( McdSlotItem& card ) { + if ( !card.IsPresent ) { + Console.WriteLn( "Error: Aborted: Convert mcd invoked but but a file is not associated." ); + return; + } + + ScopedCoreThreadClose closed_core; + + AppConfig::McdOptions config; + config.Filename = card.Filename.GetFullName(); + config.Enabled = card.IsEnabled; + config.Type = card.Type; + Dialogs::ConvertMemoryCardDialog dialog( this, m_FolderPicker->GetPath(), config ); + wxWindowID result = dialog.ShowModal(); + + if ( result != wxID_CANCEL ) { + Apply(); + RefreshSelections(); + } + + closed_core.AllowResume(); +} + bool CopyDirectory( const wxString& from, const wxString& to ) { wxDir src( from ); if ( !src.IsOpened() ) { @@ -1009,6 +1041,18 @@ void Panels::MemoryCardListPanel_Simple::OnCreateOrDeleteCard(wxCommandEvent& ev UiCreateNewCard( card ); } +void Panels::MemoryCardListPanel_Simple::OnConvertCard(wxCommandEvent& evt) { + int selectedViewIndex = m_listview->GetFirstSelected(); + if ( wxNOT_FOUND == selectedViewIndex ) { + return; + } + + McdSlotItem& card( GetCardForViewIndex( selectedViewIndex ) ); + if ( card.IsPresent ) { + UiConvertCard( card ); + } +} + //enable/disapbe port /* void Panels::MemoryCardListPanel_Simple::OnMountCard(wxCommandEvent& evt) @@ -1168,6 +1212,9 @@ void Panels::MemoryCardListPanel_Simple::OnOpenItemContextMenu(wxListEvent& evt) junk->Append( McdMenuId_Duplicate, _("Duplicate card ...") ); junk->Append( McdMenuId_Rename, _("Rename card ...") ); junk->Append( McdMenuId_Create, _("Delete card") ); + if (card.IsFormatted && !card.IsPSX) { + junk->Append( McdMenuId_Convert, _("Convert card") ); + } } else junk->Append( McdMenuId_Create, _("Create a new card ...") ); diff --git a/pcsx2/gui/Panels/MemoryCardPanels.h b/pcsx2/gui/Panels/MemoryCardPanels.h index a58646c1a..59eb6e252 100644 --- a/pcsx2/gui/Panels/MemoryCardPanels.h +++ b/pcsx2/gui/Panels/MemoryCardPanels.h @@ -212,6 +212,8 @@ namespace Panels // Doubles as Create and Delete buttons wxButton* m_button_Create; + + wxButton* m_button_Convert; // Doubles as Mount and Unmount buttons ("Enable"/"Disable" port) // wxButton* m_button_Mount; @@ -237,6 +239,7 @@ namespace Panels protected: void OnCreateOrDeleteCard(wxCommandEvent& evt); + void OnConvertCard(wxCommandEvent& evt); void OnMountCard(wxCommandEvent& evt); // void OnRelocateCard(wxCommandEvent& evt); void OnRenameFile(wxCommandEvent& evt); @@ -270,6 +273,7 @@ namespace Panels virtual void UiRenameCard( McdSlotItem& card ); virtual void UiCreateNewCard( McdSlotItem& card ); + virtual void UiConvertCard( McdSlotItem& card ); virtual void UiDeleteCard( McdSlotItem& card ); virtual void UiAssignUnassignFile( McdSlotItem& card ); diff --git a/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj b/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj index 1e469df16..bd6037fcd 100644 --- a/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj +++ b/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj @@ -646,6 +646,7 @@ + diff --git a/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj.filters b/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj.filters index 80e1532c2..c0ec451fa 100644 --- a/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj.filters +++ b/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj.filters @@ -665,6 +665,9 @@ AppHost\Dialogs + + AppHost\Dialogs + AppHost\Dialogs diff --git a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj index 5d3c284c3..d9fcf128b 100644 --- a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj +++ b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj @@ -646,6 +646,7 @@ + diff --git a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters index 065beb5da..de618d1a6 100644 --- a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters +++ b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters @@ -665,6 +665,9 @@ AppHost\Dialogs + + AppHost\Dialogs + AppHost\Dialogs From 5f8391f9f16d11043897c5c8e16a1fd71224364a Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sun, 31 May 2015 19:00:28 +0200 Subject: [PATCH 16/44] FolderMemoryCard: Fix sneaky bug that could occur in directories with odd number of files. It was possible for an invalid (because never written to, so filled with 0xFF) file entry to be recognized as valid in GetFileEntryPointer(), which cascaded up to it flushing file data as fileEntryDict data and thus losing the relevant file data page. Not sure if the other two entry accesses changed here are affected as well but better be safe than sorry, I suppose. --- pcsx2/gui/MemoryCardFolder.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 4effb1570..de815c6ea 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -520,7 +520,7 @@ u8* FolderMemoryCard::GetFileEntryPointer( const u32 currentCluster, const u32 s // check subdirectories for ( int i = 0; i < 2; ++i ) { MemoryCardFileEntry* const entry = &m_fileEntryDict[currentCluster].entries[i]; - if ( entry->IsUsed() && entry->IsDir() && entry->entry.data.cluster != 0 ) { + if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() && entry->entry.data.cluster != 0 ) { u8* ptr = GetFileEntryPointer( entry->entry.data.cluster, searchCluster, entryNumber, offset ); if ( ptr != nullptr ) { return ptr; } } @@ -533,7 +533,7 @@ MemoryCardFileEntry* FolderMemoryCard::GetFileEntryFromFileDataCluster( const u3 // check both entries of the current cluster if they're the file we're searching for, and if yes return it for ( int i = 0; i < 2; ++i ) { MemoryCardFileEntry* const entry = &m_fileEntryDict[currentCluster].entries[i]; - if ( entry->IsUsed() && entry->IsFile() ) { + if ( entry->IsValid() && entry->IsUsed() && entry->IsFile() ) { u32 fileCluster = entry->entry.data.cluster; u32 clusterNumber = 0; do { @@ -563,7 +563,7 @@ MemoryCardFileEntry* FolderMemoryCard::GetFileEntryFromFileDataCluster( const u3 // check subdirectories for ( int i = 0; i < 2; ++i ) { MemoryCardFileEntry* const entry = &m_fileEntryDict[currentCluster].entries[i]; - if ( entry->IsUsed() && entry->IsDir() && entry->entry.data.cluster != 0 ) { + if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() && entry->entry.data.cluster != 0 ) { MemoryCardFileEntry* ptr = GetFileEntryFromFileDataCluster( entry->entry.data.cluster, searchCluster, fileName, originalDirCount, outClusterNumber ); if ( ptr != nullptr ) { fileName->InsertDir( originalDirCount, wxString::FromAscii( (const char*)entry->entry.data.name ) ); From f73db1a5729313934b72430d78aef5f712fc23f5 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 1 Jun 2015 13:51:29 +0200 Subject: [PATCH 17/44] FolderMemoryCard: Optimize file access by keeping a file open between consecutive reads/writes to the same file. --- pcsx2/gui/MemoryCardFolder.cpp | 85 +++++++++++++++++++++++++++------- pcsx2/gui/MemoryCardFolder.h | 28 +++++++++++ 2 files changed, 97 insertions(+), 16 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index de815c6ea..e0f147a5e 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -39,6 +39,7 @@ void FolderMemoryCard::InitializeInternalData() { m_timeLastWritten = 0; m_isEnabled = false; m_framesUntilFlush = 0; + m_lastAccessedFile.Close(); } bool FolderMemoryCard::IsFormatted() { @@ -100,6 +101,8 @@ void FolderMemoryCard::Close() { if ( superBlockFile.IsOpened() ) { superBlockFile.Write( &m_superBlock.raw, sizeof( m_superBlock.raw ) ); } + + m_lastAccessedFile.Close(); } void FolderMemoryCard::LoadMemoryCardData( const bool enableFiltering, const wxString& filter ) { @@ -589,21 +592,21 @@ bool FolderMemoryCard::ReadFromFile( u8 *dest, u32 adr, u32 dataLength ) { if ( !fileName.DirExists() ) { fileName.Mkdir(); } - wxFFile file( fileName.GetFullPath(), L"rb" ); - if ( file.IsOpened() ) { + wxFFile* file = m_lastAccessedFile.ReOpen( fileName.GetFullPath(), L"rb" ); + if ( file->IsOpened() ) { const u32 clusterOffset = ( page % 2 ) * PageSize + offset; const u32 fileOffset = clusterNumber * ClusterSize + clusterOffset; - file.Seek( fileOffset ); - size_t bytesRead = file.Read( dest, dataLength ); + if ( fileOffset != file->Tell() ) { + file->Seek( fileOffset ); + } + size_t bytesRead = file->Read( dest, dataLength ); // if more bytes were requested than actually exist, fill the rest with 0xFF if ( bytesRead < dataLength ) { memset( &dest[bytesRead], 0xFF, dataLength - bytesRead ); } - file.Close(); - return bytesRead > 0; } } @@ -666,6 +669,8 @@ s32 FolderMemoryCard::Read( u8 *dest, u32 adr, int size ) { memcpy( dest + eccOffset, ecc, eccLength ); } + SetTimeLastReadToNow(); + // return 0 on fail, 1 on success? return 1; } @@ -712,11 +717,15 @@ 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() { - Console.WriteLn( L"(FolderMcd) Writing data for slot %u to file system.", m_slot ); + if ( m_cache.empty() ) { return; } + + Console.WriteLn( L"(FolderMcd) Writing data for slot %u to file system...", m_slot ); + const u64 timeFlushStart = wxGetLocalTimeMillis().GetValue(); // first write the superblock if necessary Flush( 0 ); @@ -762,6 +771,10 @@ void FolderMemoryCard::Flush() { Flush( i ); } + m_lastAccessedFile.Close(); + + const u64 timeFlushEnd = wxGetLocalTimeMillis().GetValue(); + Console.WriteLn( L"(FolderMcd) Done! Took %u ms.", timeFlushEnd - timeFlushStart ); } void FolderMemoryCard::Flush( const u32 page ) { @@ -873,29 +886,31 @@ bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { wxFFile createEmptyFile( fileName.GetFullPath(), L"wb" ); createEmptyFile.Close(); } - wxFFile file( fileName.GetFullPath(), L"r+b" ); - if ( file.IsOpened() ) { + wxFFile* file = m_lastAccessedFile.ReOpen( fileName.GetFullPath(), L"r+b" ); + if ( file->IsOpened() ) { const u32 clusterOffset = ( page % 2 ) * PageSize + offset; const u32 fileSize = entry->entry.data.length; const u32 fileOffsetStart = std::min( clusterNumber * ClusterSize + clusterOffset, fileSize );; const u32 fileOffsetEnd = std::min( fileOffsetStart + dataLength, fileSize ); const u32 bytesToWrite = fileOffsetEnd - fileOffsetStart; - wxFileOffset actualFileSize = file.Length(); + wxFileOffset actualFileSize = file->Length(); if ( actualFileSize < fileOffsetStart ) { + file->Seek( actualFileSize ); const u32 diff = fileOffsetStart - actualFileSize; u8 temp = 0xFF; for ( u32 i = 0; i < diff; ++i ) { - file.Write( &temp, 1 ); + file->Write( &temp, 1 ); } } - file.Seek( fileOffsetStart ); - if ( bytesToWrite > 0 ) { - file.Write( src, bytesToWrite ); + const wxFileOffset fileOffset = file->Tell(); + if ( fileOffset != fileOffsetStart ) { + file->Seek( fileOffsetStart ); + } + if ( bytesToWrite > 0 ) { + file->Write( src, bytesToWrite ); } - - file.Close(); } else { return false; } @@ -980,6 +995,10 @@ void FolderMemoryCard::SetSizeInMB( u32 megaBytes ) { SetSizeInClusters( ( megaBytes * 1024 * 1024 ) / ClusterSize ); } +void FolderMemoryCard::SetTimeLastReadToNow() { + m_framesUntilFlush = FramesAfterWriteUntilFlush; +} + void FolderMemoryCard::SetTimeLastWrittenToNow() { m_timeLastWritten = wxGetLocalTimeMillis().GetValue(); m_framesUntilFlush = FramesAfterWriteUntilFlush; @@ -1031,6 +1050,40 @@ void FolderMemoryCard::CalculateECC( u8* ecc, const u8* data ) { return; } + +FileAccessHelper::FileAccessHelper() { + m_file = nullptr; +} + +FileAccessHelper::~FileAccessHelper() { + this->Close(); +} + +wxFFile* FileAccessHelper::Open( const wxString& filename, const wxString& mode ) { + this->Close(); + m_file = new wxFFile( filename, mode ); + m_filename = filename; + m_mode = mode; + return m_file; +} + +wxFFile* FileAccessHelper::ReOpen( const wxString& filename, const wxString& mode ) { + if ( m_file && mode == m_mode && filename == m_filename ) { + return m_file; + } else { + return this->Open( filename, mode ); + } +} + +void FileAccessHelper::Close() { + if ( m_file ) { + m_file->Close(); + delete m_file; + m_file = nullptr; + } +} + + FolderMemoryCardAggregator::FolderMemoryCardAggregator() { for ( uint i = 0; i < TotalCardSlots; ++i ) { m_cards[i].SetSlot( i ); diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index a9c42df13..adea2547d 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -131,6 +131,30 @@ struct MemoryCardPage { }; #pragma pack(pop) +// -------------------------------------------------------------------------------------- +// FileAccessHelper +// -------------------------------------------------------------------------------------- +// Small helper class to keep memory card files opened between calls to Read()/Save() +class FileAccessHelper { +protected: + wxFFile* m_file; + wxString m_filename; + wxString m_mode; + +public: + FileAccessHelper(); + ~FileAccessHelper(); + + // Get an already opened file if possible, or open a new one and remember it + wxFFile* ReOpen( const wxString& filename, const wxString& mode ); + // Close an open file, if any + void Close(); + +protected: + // Open a new file and remember it for later + wxFFile* Open( const wxString& filename, const wxString& mode ); +}; + // -------------------------------------------------------------------------------------- // FolderMemoryCard // -------------------------------------------------------------------------------------- @@ -180,6 +204,9 @@ protected: // used to figure out if contents were changed for savestate-related purposes, see GetCRC() u64 m_timeLastWritten; + // remembers and keeps the last accessed file open for further access + FileAccessHelper m_lastAccessedFile; + // path to the folder that contains the files of this memory card wxFileName m_folderName; @@ -316,6 +343,7 @@ protected: // write data as Save() normally would, but ignore the cache; used for flushing s32 WriteWithoutCache( const u8 *src, u32 adr, int size ); + void SetTimeLastReadToNow(); void SetTimeLastWrittenToNow(); From 41c3eacb6fbd9ae33fecb7985d71752e9d175cc5 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 1 Jun 2015 18:18:15 +0200 Subject: [PATCH 18/44] FolderMemoryCard: Move file existence check into the helper class, so that it gets called much less often. This *drastically* increases performance, bizarrely enough. --- pcsx2/gui/MemoryCardFolder.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index e0f147a5e..0f243a5d7 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -589,9 +589,6 @@ bool FolderMemoryCard::ReadFromFile( u8 *dest, u32 adr, u32 dataLength ) { u32 clusterNumber; const MemoryCardFileEntry* const entry = GetFileEntryFromFileDataCluster( m_superBlock.data.rootdir_cluster, fatCluster, &fileName, fileName.GetDirCount(), &clusterNumber ); if ( entry != nullptr ) { - if ( !fileName.DirExists() ) { - fileName.Mkdir(); - } wxFFile* file = m_lastAccessedFile.ReOpen( fileName.GetFullPath(), L"rb" ); if ( file->IsOpened() ) { const u32 clusterOffset = ( page % 2 ) * PageSize + offset; @@ -879,13 +876,6 @@ bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { u32 clusterNumber; const MemoryCardFileEntry* const entry = GetFileEntryFromFileDataCluster( m_superBlock.data.rootdir_cluster, fatCluster, &fileName, fileName.GetDirCount(), &clusterNumber ); if ( entry != nullptr ) { - if ( !fileName.DirExists() ) { - fileName.Mkdir(); - } - if ( !fileName.FileExists() ) { - wxFFile createEmptyFile( fileName.GetFullPath(), L"wb" ); - createEmptyFile.Close(); - } wxFFile* file = m_lastAccessedFile.ReOpen( fileName.GetFullPath(), L"r+b" ); if ( file->IsOpened() ) { const u32 clusterOffset = ( page % 2 ) * PageSize + offset; @@ -1061,6 +1051,16 @@ FileAccessHelper::~FileAccessHelper() { wxFFile* FileAccessHelper::Open( const wxString& filename, const wxString& mode ) { this->Close(); + + wxFileName fn( filename ); + if ( !fn.FileExists() ) { + if ( !fn.DirExists() ) { + fn.Mkdir(); + } + wxFFile createEmptyFile( filename, L"wb" ); + createEmptyFile.Close(); + } + m_file = new wxFFile( filename, mode ); m_filename = filename; m_mode = mode; From f731e3dc1b2349cf5acd3ce20070fb40687395fd Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Tue, 2 Jun 2015 01:38:48 +0200 Subject: [PATCH 19/44] FolderMemoryCard: On reads and writes to actual data, check if the relevant data is actually in use according to the FAT. This allows us to skip a bunch of accesses trying to find a matching file or memory location, presumably without actual consequences. This isn't really gonna change much in actual game use, but does speed up conversions of FileMemoryCards. --- pcsx2/gui/MemoryCardFolder.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 0f243a5d7..a038a4a60 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -474,6 +474,10 @@ u8* FolderMemoryCard::GetSystemBlockPointer( const u32 adr ) { if ( cluster >= startDataCluster && cluster < endDataCluster ) { // trying to access a file entry? const u32 fatCluster = cluster - m_superBlock.data.alloc_offset; + // if this cluster is unused according to FAT, we can assume we won't find anything + if ( ( m_fat.data[0][0][fatCluster] & 0x80000000 ) == 0 ) { + return nullptr; + } return GetFileEntryPointer( m_superBlock.data.rootdir_cluster, fatCluster, page % 2, offset ); } @@ -584,6 +588,11 @@ bool FolderMemoryCard::ReadFromFile( u8 *dest, u32 adr, u32 dataLength ) { const u32 cluster = adr / ClusterSizeRaw; const u32 fatCluster = cluster - m_superBlock.data.alloc_offset; + // if the cluster is unused according to FAT, just return + if ( ( m_fat.data[0][0][fatCluster] & 0x80000000 ) == 0 ) { + return false; + } + // figure out which file to read from wxFileName fileName( m_folderName ); u32 clusterNumber; @@ -871,6 +880,11 @@ bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { const u32 offset = adr % PageSizeRaw; const u32 fatCluster = cluster - m_superBlock.data.alloc_offset; + // if the cluster is unused according to FAT, just skip all this, we're not gonna find anything anyway + if ( ( m_fat.data[0][0][fatCluster] & 0x80000000 ) == 0 ) { + return false; + } + // figure out which file to write to wxFileName fileName( m_folderName ); u32 clusterNumber; From 541a254c60a102bbc7fb68560d3bb7dd588548eb Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Tue, 2 Jun 2015 19:29:06 +0200 Subject: [PATCH 20/44] FolderMemoryCard: Clean up Flush logic. --- pcsx2/gui/MemoryCardFolder.cpp | 35 +++++++++++++++++++--------------- pcsx2/gui/MemoryCardFolder.h | 8 +++++++- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index a038a4a60..51b9d9f9e 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -734,7 +734,7 @@ void FolderMemoryCard::Flush() { const u64 timeFlushStart = wxGetLocalTimeMillis().GetValue(); // first write the superblock if necessary - Flush( 0 ); + FlushBlock( 0 ); if ( !IsFormatted() ) { return; } const u32 clusterCount = GetSizeInClusters(); @@ -744,9 +744,7 @@ void FolderMemoryCard::Flush() { for ( int i = 0; i < IndirectFatClusterCount; ++i ) { const u32 cluster = m_superBlock.data.ifc_list[i]; if ( cluster > 0 && cluster < clusterCount ) { - const u32 page = cluster * 2; - Flush( page ); - Flush( page + 1 ); + FlushCluster( cluster ); } } @@ -755,18 +753,14 @@ void FolderMemoryCard::Flush() { for ( int j = 0; j < ClusterSize / 4; ++j ) { const u32 cluster = m_indirectFat.data[i][j]; if ( cluster > 0 && cluster < clusterCount ) { - const u32 page = cluster * 2; - Flush( page ); - Flush( page + 1 ); + FlushCluster( cluster ); } } } // then all directory and file entries const u32 rootDirCluster = m_superBlock.data.rootdir_cluster; - const u32 rootDirPage = ( rootDirCluster + m_superBlock.data.alloc_offset ) * 2; - Flush( rootDirPage ); - Flush( rootDirPage + 1 ); + FlushCluster( rootDirCluster + m_superBlock.data.alloc_offset ); MemoryCardFileEntryCluster* rootEntries = &m_fileEntryDict[rootDirCluster]; if ( rootEntries->entries[0].IsValid() && rootEntries->entries[0].IsUsed() ) { FlushFileEntries( rootDirCluster, rootEntries->entries[0].entry.data.length ); @@ -774,7 +768,7 @@ void FolderMemoryCard::Flush() { // and finally, flush everything that hasn't been flushed yet for ( uint i = 0; i < pageCount; ++i ) { - Flush( i ); + FlushPage( i ); } m_lastAccessedFile.Close(); @@ -783,7 +777,7 @@ void FolderMemoryCard::Flush() { Console.WriteLn( L"(FolderMcd) Done! Took %u ms.", timeFlushEnd - timeFlushStart ); } -void FolderMemoryCard::Flush( const u32 page ) { +void FolderMemoryCard::FlushPage( const u32 page ) { auto it = m_cache.find( page ); if ( it != m_cache.end() ) { WriteWithoutCache( &it->second.raw[0], page * PageSizeRaw, PageSize ); @@ -791,11 +785,22 @@ void FolderMemoryCard::Flush( const u32 page ) { } } +void FolderMemoryCard::FlushCluster( const u32 cluster ) { + const u32 page = cluster * 2; + FlushPage( page ); + FlushPage( page + 1 ); +} + +void FolderMemoryCard::FlushBlock( const u32 block ) { + const u32 page = block * 16; + for ( int i = 0; i < 16; ++i ) { + FlushPage( page + i ); + } +} + void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remainingFiles, const wxString& dirPath ) { // flush the current cluster - const u32 page = ( dirCluster + m_superBlock.data.alloc_offset ) * 2; - Flush( page ); - Flush( page + 1 ); + FlushCluster( dirCluster + m_superBlock.data.alloc_offset ); // if either of the current entries is a subdir, flush that too MemoryCardFileEntryCluster* entries = &m_fileEntryDict[dirCluster]; diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index adea2547d..0a58ee030 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -335,7 +335,13 @@ protected: void Flush(); // flush a single page of the cache to the internal data and/or host file system - void Flush( const u32 page ); + void FlushPage( const u32 page ); + + // flush a memory card cluster of the cache to the internal data and/or host file system + void FlushCluster( const u32 cluster ); + + // flush a whole memory card block of the cache to the internal data and/or host file system + void FlushBlock( const u32 block ); // 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"" ); From c3694c4ad187d52ce815448491c482a5f5bff91f Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Tue, 2 Jun 2015 19:42:39 +0200 Subject: [PATCH 21/44] FolderMemoryCard: Abort Flush operation when remnants of an incomplete save operation are found. --- pcsx2/gui/MemoryCardFolder.cpp | 10 +++++++++- pcsx2/gui/MemoryCardFolder.h | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 51b9d9f9e..4e060ad48 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -487,7 +487,7 @@ u8* FolderMemoryCard::GetSystemBlockPointer( const u32 adr ) { } else if ( block == m_superBlock.data.backup_block1 ) { src = &m_backupBlock1[( page % 16 ) * PageSize + offset]; } else if ( block == m_superBlock.data.backup_block2 ) { - src = &m_backupBlock2[( page % 16 ) * PageSize + offset]; + src = &m_backupBlock2.raw[( page % 16 ) * PageSize + offset]; } else { // trying to access indirect FAT? for ( int i = 0; i < IndirectFatClusterCount; ++i ) { @@ -737,6 +737,14 @@ void FolderMemoryCard::Flush() { FlushBlock( 0 ); if ( !IsFormatted() ) { return; } + // check if we were interrupted in the middle of a save operation, if yes abort + FlushBlock( m_superBlock.data.backup_block1 ); + FlushBlock( m_superBlock.data.backup_block2 ); + if ( m_backupBlock2.programmedBlock != 0xFFFFFFFFu ) { + Console.Warning( L"(FolderMcd) Aborting flush of slot %u, emulation was interrupted during save process!", m_slot ); + return; + } + const u32 clusterCount = GetSizeInClusters(); const u32 pageCount = clusterCount * 2; diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 0a58ee030..5b14cc865 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -191,7 +191,10 @@ protected: u8 raw[IndirectFatClusterCount][ClusterSize / 4][ClusterSize]; } m_fat; u8 m_backupBlock1[BlockSize]; - u8 m_backupBlock2[BlockSize]; + union backupBlock2Union { + u32 programmedBlock; + u8 raw[BlockSize]; + } m_backupBlock2; // stores directory and file metadata std::map m_fileEntryDict; From bee4f0578d2050074e7fba95b6d3939df2577ccf Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Tue, 9 Jun 2015 00:29:06 +0200 Subject: [PATCH 22/44] GameIndex.dbf: Add Armored Core Memcard filters. --- bin/GameIndex.dbf | 48 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/bin/GameIndex.dbf b/bin/GameIndex.dbf index 551b931ca..e6d223235 100644 --- a/bin/GameIndex.dbf +++ b/bin/GameIndex.dbf @@ -263,6 +263,7 @@ Region = NTSC-Unk Serial = SCAJ-20011 Name = Armored Core 3 - Silent Line Region = NTSC-Unk +MemCardFilter = SCAJ-20011/SCPS-55014/SLPS-25112/SLPS-25169/SLPS-73417/SLPS-73420 --------------------------------------------- Serial = SCAJ-20012 Name = Venus & Braves @@ -491,8 +492,14 @@ Name = Dragon Quest V - Bride of the Sky Region = NTSC-Unk --------------------------------------------- Serial = SCAJ-20076 -Name = Armored Core - Nexus +Name = Armored Core - Nexus [Disc 1] Region = NTSC-Unk +MemCardFilter = SCAJ-20076/SCAJ-20077/SLPS-25338/SLPS-25339/SLPS-73202/SLPS-73203 +--------------------------------------------- +Serial = SCAJ-20077 +Name = Armored Core - Nexus [Disc 2] +Region = NTSC-Unk +MemCardFilter = SCAJ-20076/SCAJ-20077/SLPS-25338/SLPS-25339/SLPS-73202/SLPS-73203 --------------------------------------------- Serial = SCAJ-20078 Name = Kuon @@ -2886,6 +2893,7 @@ Compat = 5 Serial = SCKA-20047 Name = Armored Core Nine Breaker Region = NTSC-K +MemCardFilter = SCKA-20047/SLKA-25201/SLKA-25202 --------------------------------------------- Serial = SCKA-20048 Name = Killzone @@ -4200,6 +4208,7 @@ Region = NTSC-J Serial = SCPS-55024 Name = Armored Core 2 - Another Age Region = NTSC-J +MemCardFilter = SCPS-55024/SLPS-25007/SLPS-25040/SLPS-73403/SLPS-73411 --------------------------------------------- Serial = SCPS-55025 Name = Eve of Extinction @@ -8428,6 +8437,7 @@ Region = PAL-Unk Serial = SLES-50905 Name = Armored Core 2 - Another Age Region = PAL-Unk +// save import option was removed from PAL release --------------------------------------------- Serial = SLES-50906 Name = Master Rally @@ -11065,6 +11075,7 @@ Serial = SLES-52203 Name = Armored Core - Silent Line Region = PAL-Unk Compat = 5 +MemCardFilter = SLES-51399/SLES-52203 --------------------------------------------- Serial = SLES-52204 Name = UFC Sudden Impact @@ -14679,6 +14690,7 @@ Region = PAL-Unk Serial = SLES-53819 Name = Armored Core - Nine Breaker Region = PAL-Unk +MemCardFilter = SLES-53819/SLES-82036/SLES-82037 --------------------------------------------- Serial = SLES-53820 Name = Armored Core - Last Raven @@ -17616,10 +17628,12 @@ MemCardFilter = SLES-82034/SCES-82034 Serial = SLES-82036 Name = Armored Core - Nexus [Evolution Disc] Region = PAL-M5 +MemCardFilter = SLES-82036/SLES-82037 --------------------------------------------- Serial = SLES-82037 Name = Armored Core - Nexus [Revolution Disc] Region = PAL-M5 +MemCardFilter = SLES-82036/SLES-82037 --------------------------------------------- Serial = SLES-82038 Name = Onimusha - Dawn of Dreams [Disc1] @@ -17774,6 +17788,7 @@ Region = NTSC-K Serial = SLKA-25041 Name = Armored Core 3 Silent Line Region = NTSC-K +MemCardFilter = SLKA-25041/SLPM-67524 --------------------------------------------- Serial = SLKA-25042 Name = Tamamayu Monogatari 2 Horobi no Mushi @@ -18016,10 +18031,12 @@ Region = NTSC-K Serial = SLKA-25201 Name = Armored Core Nexus Evolution DISC1 Region = NTSC-K +MemCardFilter = SLKA-25201/SLKA-25202 --------------------------------------------- Serial = SLKA-25202 Name = Armored Core Nexus Revolution DISC2 Region = NTSC-K +MemCardFilter = SLKA-25201/SLKA-25202 --------------------------------------------- Serial = SLKA-25204 Name = Showdown - Legends of Wrestling @@ -30127,6 +30144,7 @@ Serial = SLPS-25040 Name = Armored Core 2 - Another Age Region = NTSC-J Compat = 5 +MemCardFilter = SCPS-55024/SLPS-25007/SLPS-25040/SLPS-73403/SLPS-73411 --------------------------------------------- Serial = SLPS-25041 Name = Shadow Hearts @@ -30517,6 +30535,7 @@ Region = NTSC-J Serial = SLPS-25169 Name = Armored Core 3 - Silent Line Region = NTSC-J +MemCardFilter = SCAJ-20011/SCPS-55014/SLPS-25112/SLPS-25169/SLPS-73417/SLPS-73420 --------------------------------------------- Serial = SLPS-25170 Name = SD Gundam G Generation Neo @@ -31072,8 +31091,14 @@ Name = Bass Landing 3 [Sammy Best] Region = NTSC-J --------------------------------------------- Serial = SLPS-25338 -Name = Armored Core - Nexus +Name = Armored Core - Nexus [Disc 1] Region = NTSC-J +MemCardFilter = SCAJ-20076/SCAJ-20077/SLPS-25338/SLPS-25339/SLPS-73202/SLPS-73203 +--------------------------------------------- +Serial = SLPS-25339 +Name = Armored Core - Nexus [Disc 2] +Region = NTSC-J +MemCardFilter = SCAJ-20076/SCAJ-20077/SLPS-25338/SLPS-25339/SLPS-73202/SLPS-73203 --------------------------------------------- Serial = SLPS-25340 Name = Missing Parts - Side B @@ -31358,6 +31383,7 @@ Region = NTSC-J Serial = SLPS-25408 Name = Armored Core - Nine Breaker Region = NTSC-J +MemCardFilter = SLPS-25408/SCAJ-20076/SCAJ-20077/SLPS-25338/SLPS-25339/SLPS-73202/SLPS-73203 --------------------------------------------- Serial = SLPS-25409 Name = Futakoi [Limited Edition] @@ -33307,8 +33333,14 @@ Name = Fatal Frame 2 - Crimson Butterfly [PlayStation 2 The Best] Region = NTSC-J --------------------------------------------- Serial = SLPS-73202 -Name = Armored Core - Nexus [PlayStation 2 The Best] +Name = Armored Core - Nexus [PlayStation 2 The Best] [Disc 1] Region = NTSC-J +MemCardFilter = SCAJ-20076/SCAJ-20077/SLPS-25338/SLPS-25339/SLPS-73202/SLPS-73203 +--------------------------------------------- +Serial = SLPS-73203 +Name = Armored Core - Nexus [PlayStation 2 The Best] [Disc 2] +Region = NTSC-J +MemCardFilter = SCAJ-20076/SCAJ-20077/SLPS-25338/SLPS-25339/SLPS-73202/SLPS-73203 --------------------------------------------- Serial = SLPS-73204 Name = Zettai Zetsumi Toshi [PlayStation 2 The Best] @@ -33578,6 +33610,7 @@ Region = NTSC-J Serial = SLPS-73411 Name = Armored Core 2 - Another Age [PlayStation 2 The Best] Region = NTSC-J +MemCardFilter = SCPS-55024/SLPS-25007/SLPS-25040/SLPS-73403/SLPS-73411 --------------------------------------------- Serial = SLPS-73412 Name = Vampire Night [PlayStation 2 The Best] @@ -33607,6 +33640,7 @@ Region = NTSC-J Serial = SLPS-73420 Name = Armored Core 3 - Silent Line [PlayStation 2 The Best] Region = NTSC-J +MemCardFilter = SCAJ-20011/SCPS-55014/SLPS-25112/SLPS-25169/SLPS-73417/SLPS-73420 --------------------------------------------- Serial = SLPS-73421 Name = Tenchu 3 [PlayStation 2 The Best] @@ -34636,6 +34670,8 @@ Compat = 5 Serial = SLUS-20249 Name = Armored Core 2 - Another Age Region = NTSC-U +// can import data from regular Armored Core 2 +MemCardFilter = SLUS-20249/SLUS-20014 --------------------------------------------- Serial = SLUS-20250 Name = Stuntman @@ -36394,6 +36430,8 @@ Compat = 5 Serial = SLUS-20644 Name = Armored Core - Silent Line Region = NTSC-U +// can import data from AC3 +MemCardFilter = SLUS-20435/SLUS-20644 --------------------------------------------- Serial = SLUS-20645 Name = Time Crisis 3 [with Guncon] @@ -37984,6 +38022,7 @@ Serial = SLUS-20986 Name = Armored Core Nexus [Evolution Disc] Region = NTSC-U Compat = 5 +MemCardFilter = SLUS-20986/SLUS-21079 --------------------------------------------- Serial = SLUS-20987 Name = Pool Paradise @@ -38440,6 +38479,7 @@ Compat = 5 Serial = SLUS-21079 Name = Armored Core Nexus [Revolution Disc] Region = NTSC-U +MemCardFilter = SLUS-20986/SLUS-21079 --------------------------------------------- Serial = SLUS-21080 Name = Samurai Warriors - Xtreme Legends @@ -39010,6 +39050,8 @@ Serial = SLUS-21200 Name = Armored Core - Nine Breaker Region = NTSC-U Compat = 5 +// can import data from AC:Nexus +MemCardFilter = SLUS-21200/SLUS-20986/SLUS-21079 --------------------------------------------- Serial = SLUS-21201 Name = Tales of Legendia From 80feb1087ce0d7cc7997416698200029dbe66148 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Tue, 9 Jun 2015 23:44:00 +0200 Subject: [PATCH 23/44] FolderMemoryCard: Add a helper structure to quickly access a file entry from a file data FAT cluster. Speeds up file access, especially when a lot of files are loaded in the virtual memory card. --- pcsx2/gui/MemoryCardFolder.cpp | 91 ++++++++++++++++++++++++++-------- pcsx2/gui/MemoryCardFolder.h | 29 +++++++++-- 2 files changed, 96 insertions(+), 24 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 4e060ad48..18a6875f0 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -36,6 +36,7 @@ void FolderMemoryCard::InitializeInternalData() { memset( &m_backupBlock1, 0xFF, sizeof( m_backupBlock1 ) ); memset( &m_backupBlock2, 0xFF, sizeof( m_backupBlock2 ) ); m_cache.clear(); + m_fileMetadataQuickAccess.clear(); m_timeLastWritten = 0; m_isEnabled = false; m_framesUntilFlush = 0; @@ -102,6 +103,8 @@ void FolderMemoryCard::Close() { superBlockFile.Write( &m_superBlock.raw, sizeof( m_superBlock.raw ) ); } + m_cache.clear(); + m_fileMetadataQuickAccess.clear(); m_lastAccessedFile.Close(); } @@ -128,7 +131,7 @@ void FolderMemoryCard::LoadMemoryCardData( const bool enableFiltering, const wxS CreateFat(); CreateRootDir(); MemoryCardFileEntry* const rootDirEntry = &m_fileEntryDict[m_superBlock.data.rootdir_cluster].entries[0]; - AddFolder( rootDirEntry, m_folderName.GetPath(), enableFiltering, filter ); + AddFolder( rootDirEntry, m_folderName.GetPath(), nullptr, enableFiltering, filter ); } } @@ -270,7 +273,7 @@ bool FilterMatches( const wxString& fileName, const wxString& filter ) { return false; } -bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const bool enableFiltering, const wxString& filter ) { +bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, MemoryCardFileMetadataReference* parent, const bool enableFiltering, const wxString& filter ) { wxDir dir( dirPath ); if ( dir.IsOpened() ) { Console.WriteLn( L"(FolderMcd) Adding folder: %s", WX_STR( dirPath ) ); @@ -302,7 +305,7 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS bool isFile = wxFile::Exists( fileInfo.GetFullPath() ); if ( isFile ) { - if ( AddFile( dirEntry, dirPath, fileName ) ) { + if ( AddFile( dirEntry, dirPath, fileName, parent ) ) { ++entryNumber; } } else { @@ -365,10 +368,12 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS subDirCluster->entries[1].entry.data.name[0] = '.'; subDirCluster->entries[1].entry.data.name[1] = '.'; + MemoryCardFileMetadataReference* dirRef = AddDirEntryToMetadataQuickAccess( newDirEntry, parent ); + ++entryNumber; // and add all files in subdir - AddFolder( newDirEntry, fileInfo.GetFullPath() ); + AddFolder( newDirEntry, fileInfo.GetFullPath(), dirRef ); } hasNext = dir.GetNext( &fileName ); @@ -380,7 +385,7 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS return false; } -bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName ) { +bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName, MemoryCardFileMetadataReference* parent ) { wxFileName relativeFilePath( dirPath, fileName ); relativeFilePath.MakeRelativeTo( m_folderName.GetPath() ); Console.WriteLn( L"(FolderMcd) Adding file: %s", WX_STR( relativeFilePath.GetFullPath() ) ); @@ -434,6 +439,8 @@ bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxStr } file.Close(); + + AddFileEntryToMetadataQuickAccess( newFileEntry, parent ); } else { Console.WriteLn( L"(FolderMcd) Could not open file: %s", WX_STR( relativeFilePath.GetFullPath() ) ); return false; @@ -445,6 +452,44 @@ bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxStr return true; } +MemoryCardFileMetadataReference* FolderMemoryCard::AddDirEntryToMetadataQuickAccess( MemoryCardFileEntry* const entry, MemoryCardFileMetadataReference* const parent ) { + MemoryCardFileMetadataReference* ref = &m_fileMetadataQuickAccess[entry->entry.data.cluster]; + ref->parent = parent; + ref->entry = entry; + ref->consecutiveCluster = 0xFFFFFFFFu; + return ref; +} + +void FolderMemoryCard::AddFileEntryToMetadataQuickAccess( MemoryCardFileEntry* const entry, MemoryCardFileMetadataReference* const parent ) { + u32 fileCluster = entry->entry.data.cluster; + + // zero-length files have no file clusters + if ( fileCluster == 0xFFFFFFFFu ) { + return; + } + + u32 clusterNumber = 0; + do { + MemoryCardFileMetadataReference* ref = &m_fileMetadataQuickAccess[fileCluster & 0x7FFFFFFFu]; + ref->parent = parent; + ref->entry = entry; + ref->consecutiveCluster = clusterNumber; + ++clusterNumber; + } while ( ( fileCluster = m_fat.data[0][0][fileCluster] ) != 0xFFFFFFFFu ); +} + +void MemoryCardFileMetadataReference::GetPath( wxFileName* fileName ) { + if ( parent ) { + parent->GetPath( fileName ); + } + + if ( entry->IsDir() ) { + fileName->AppendDir( wxString::FromAscii( (const char*)entry->entry.data.name ) ); + } else if ( entry->IsFile() ) { + fileName->SetName( wxString::FromAscii( (const char*)entry->entry.data.name ) ); + } +} + s32 FolderMemoryCard::IsPresent() { return m_isEnabled; } @@ -536,6 +581,8 @@ u8* FolderMemoryCard::GetFileEntryPointer( const u32 currentCluster, const u32 s return nullptr; } +// This method is actually unused since the introduction of m_fileMetadataQuickAccess. +// I'll leave it here anyway though to show how you traverse the file system. MemoryCardFileEntry* FolderMemoryCard::GetFileEntryFromFileDataCluster( const u32 currentCluster, const u32 searchCluster, wxFileName* fileName, const size_t originalDirCount, u32* outClusterNumber ) { // check both entries of the current cluster if they're the file we're searching for, and if yes return it for ( int i = 0; i < 2; ++i ) { @@ -551,11 +598,6 @@ MemoryCardFileEntry* FolderMemoryCard::GetFileEntryFromFileDataCluster( const u3 } ++clusterNumber; } while ( ( fileCluster = m_fat.data[0][0][fileCluster] & 0x7FFFFFFF ) != 0x7FFFFFFF ); - // There's a lot of optimization work that can be done here, looping through all clusters of every single file - // is not very efficient, especially since files are going to be accessed from the start and in-order the vast - // majority of the time. You can probably cut a lot of the work by remembering the state of the last access - // and only checking if the current access is either the same or the next cluster according to the FAT. - //} while ( false ); } } @@ -594,10 +636,11 @@ bool FolderMemoryCard::ReadFromFile( u8 *dest, u32 adr, u32 dataLength ) { } // figure out which file to read from - wxFileName fileName( m_folderName ); - u32 clusterNumber; - const MemoryCardFileEntry* const entry = GetFileEntryFromFileDataCluster( m_superBlock.data.rootdir_cluster, fatCluster, &fileName, fileName.GetDirCount(), &clusterNumber ); - if ( entry != nullptr ) { + auto it = m_fileMetadataQuickAccess.find( fatCluster ); + if ( it != m_fileMetadataQuickAccess.end() ) { + wxFileName fileName( m_folderName ); + const u32 clusterNumber = it->second.consecutiveCluster; + it->second.GetPath( &fileName ); wxFFile* file = m_lastAccessedFile.ReOpen( fileName.GetFullPath(), L"rb" ); if ( file->IsOpened() ) { const u32 clusterOffset = ( page % 2 ) * PageSize + offset; @@ -806,7 +849,7 @@ void FolderMemoryCard::FlushBlock( const u32 block ) { } } -void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remainingFiles, const wxString& dirPath ) { +void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remainingFiles, const wxString& dirPath, MemoryCardFileMetadataReference* parent ) { // flush the current cluster FlushCluster( dirCluster + m_superBlock.data.alloc_offset ); @@ -839,15 +882,19 @@ void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remaini } } - FlushFileEntries( cluster, entry->entry.data.length, subDirPath ); + MemoryCardFileMetadataReference* dirRef = AddDirEntryToMetadataQuickAccess( entry, parent ); + + FlushFileEntries( cluster, entry->entry.data.length, subDirPath, dirRef ); } + } else if ( entry->IsValid() && entry->IsUsed() && entry->IsFile() ) { + AddFileEntryToMetadataQuickAccess( entry, parent ); } } // continue to the next cluster of this directory const u32 nextCluster = m_fat.data[0][0][dirCluster]; if ( nextCluster != 0xFFFFFFFF ) { - FlushFileEntries( nextCluster & 0x7FFFFFFF, remainingFiles - 2, dirPath ); + FlushFileEntries( nextCluster & 0x7FFFFFFF, remainingFiles - 2, dirPath, parent ); } } @@ -899,10 +946,12 @@ bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { } // figure out which file to write to - wxFileName fileName( m_folderName ); - u32 clusterNumber; - const MemoryCardFileEntry* const entry = GetFileEntryFromFileDataCluster( m_superBlock.data.rootdir_cluster, fatCluster, &fileName, fileName.GetDirCount(), &clusterNumber ); - if ( entry != nullptr ) { + auto it = m_fileMetadataQuickAccess.find( fatCluster ); + if ( it != m_fileMetadataQuickAccess.end() ) { + wxFileName fileName( m_folderName ); + const MemoryCardFileEntry* const entry = it->second.entry; + const u32 clusterNumber = it->second.consecutiveCluster; + it->second.GetPath( &fileName ); wxFFile* file = m_lastAccessedFile.ReOpen( fileName.GetFullPath(), L"r+b" ); if ( file->IsOpened() ) { const u32 clusterOffset = ( page % 2 ) * PageSize + offset; diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 5b14cc865..1076142e3 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -131,6 +131,18 @@ struct MemoryCardPage { }; #pragma pack(pop) +// -------------------------------------------------------------------------------------- +// MemoryCardFileMetadataReference +// -------------------------------------------------------------------------------------- +// Helper structure to quickly access file entries from any file data FAT cluster +struct MemoryCardFileMetadataReference { + MemoryCardFileMetadataReference* parent; + MemoryCardFileEntry* entry; + u32 consecutiveCluster; + + void GetPath( wxFileName* fileName ); +}; + // -------------------------------------------------------------------------------------- // FileAccessHelper // -------------------------------------------------------------------------------------- @@ -198,6 +210,8 @@ protected: // stores directory and file metadata std::map m_fileEntryDict; + // quick-access map of related file entry metadata for each memory card FAT cluster that contains file data + std::map m_fileMetadataQuickAccess; // holds a copy of modified pages of the memory card before they're flushed to the file system std::map m_cache; @@ -320,14 +334,23 @@ protected: // adds a folder in the host file system to the memory card, including all files and subdirectories // - dirEntry: the entry of the directory in the parent directory, or the root "." entry // - dirPath: the full path to the directory in the host file system + // - parent: pointer to the parent dir's quick-access reference element // - enableFiltering and filter: filter loaded contents, see LoadMemoryCardData() - bool AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const bool enableFiltering = false, const wxString& filter = L"" ); + bool AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, MemoryCardFileMetadataReference* parent = nullptr, const bool enableFiltering = false, const wxString& filter = L"" ); // adds a file in the host file sytem to the memory card // - dirEntry: the entry of the directory in the parent directory, or the root "." entry // - dirPath: the full path to the directory containing the file in the host file system // - fileName: the name of the file, without path - bool AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName ); + // - parent: pointer to the parent dir's quick-access reference element + bool AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName, MemoryCardFileMetadataReference* parent = nullptr ); + + + // adds a file to the quick-access dictionary, so it can be accessed more efficiently (ie, without searching through the entire file system) later + void AddFileEntryToMetadataQuickAccess( MemoryCardFileEntry* const entry, MemoryCardFileMetadataReference* const parent ); + + // creates a reference to a directory entry, so it can be passed as parent to other files/directories + MemoryCardFileMetadataReference* AddDirEntryToMetadataQuickAccess( MemoryCardFileEntry* const entry, MemoryCardFileMetadataReference* const parent ); bool ReadFromFile( u8 *dest, u32 adr, u32 dataLength ); @@ -347,7 +370,7 @@ protected: void FlushBlock( const u32 block ); // 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"" ); + void FlushFileEntries( const u32 dirCluster, const u32 remainingFiles, const wxString& dirPath = L"", MemoryCardFileMetadataReference* parent = nullptr ); // write data as Save() normally would, but ignore the cache; used for flushing s32 WriteWithoutCache( const u8 *src, u32 adr, int size ); From 385bdfb8697a66dda63c4731ac00c1ed29b313f3 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Thu, 11 Jun 2015 03:06:15 +0200 Subject: [PATCH 24/44] FolderMemoryCard: Further optimize file access times. * Reduce the amount of times path strings are constructed. * Move file metadata writing to the file helper, which means it will only be written once per consecutive file access instead of on every file chunk. --- pcsx2/gui/MemoryCardFolder.cpp | 76 ++++++++++++++++++---------------- pcsx2/gui/MemoryCardFolder.h | 6 +-- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 18a6875f0..647672921 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -638,10 +638,8 @@ bool FolderMemoryCard::ReadFromFile( u8 *dest, u32 adr, u32 dataLength ) { // figure out which file to read from auto it = m_fileMetadataQuickAccess.find( fatCluster ); if ( it != m_fileMetadataQuickAccess.end() ) { - wxFileName fileName( m_folderName ); const u32 clusterNumber = it->second.consecutiveCluster; - it->second.GetPath( &fileName ); - wxFFile* file = m_lastAccessedFile.ReOpen( fileName.GetFullPath(), L"rb" ); + wxFFile* file = m_lastAccessedFile.ReOpen( m_folderName, &it->second, L"rb" ); if ( file->IsOpened() ) { const u32 clusterOffset = ( page % 2 ) * PageSize + offset; const u32 fileOffset = clusterNumber * ClusterSize + clusterOffset; @@ -948,11 +946,9 @@ bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { // figure out which file to write to auto it = m_fileMetadataQuickAccess.find( fatCluster ); if ( it != m_fileMetadataQuickAccess.end() ) { - wxFileName fileName( m_folderName ); const MemoryCardFileEntry* const entry = it->second.entry; const u32 clusterNumber = it->second.consecutiveCluster; - it->second.GetPath( &fileName ); - wxFFile* file = m_lastAccessedFile.ReOpen( fileName.GetFullPath(), L"r+b" ); + wxFFile* file = m_lastAccessedFile.ReOpen( m_folderName, &it->second, L"r+b", true ); if ( file->IsOpened() ) { const u32 clusterOffset = ( page % 2 ) * PageSize + offset; const u32 fileSize = entry->entry.data.length; @@ -981,30 +977,6 @@ bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { return false; } - // separately write metadata of file if it's nonstandard - fileName.AppendDir( L"_pcsx2_meta" ); - if ( entry->entry.data.mode != MemoryCardFileEntry::DefaultFileMode || entry->entry.data.attr != 0 ) { - if ( !fileName.DirExists() ) { - fileName.Mkdir(); - } - wxFFile metaFile( fileName.GetFullPath(), L"wb" ); - if ( metaFile.IsOpened() ) { - metaFile.Write( entry->entry.raw, 0x40 ); - metaFile.Close(); - } - } else { - // if metadata is standard remove metadata file if it exists - if ( fileName.FileExists() ) { - wxRemoveFile( fileName.GetFullPath() ); - - // and remove the metadata dir if it's now empty - wxDir metaDir( fileName.GetPath() ); - if ( metaDir.IsOpened() && !metaDir.HasFiles() ) { - wxRmdir( fileName.GetPath() ); - } - } - } - return true; } @@ -1125,10 +1097,13 @@ FileAccessHelper::~FileAccessHelper() { this->Close(); } -wxFFile* FileAccessHelper::Open( const wxString& filename, const wxString& mode ) { +wxFFile* FileAccessHelper::Open( const wxFileName& folderName, MemoryCardFileMetadataReference* fileRef, const wxString& mode, bool writeMetadata ) { this->Close(); - wxFileName fn( filename ); + wxFileName fn( folderName ); + fileRef->GetPath( &fn ); + wxString filename( fn.GetFullPath() ); + if ( !fn.FileExists() ) { if ( !fn.DirExists() ) { fn.Mkdir(); @@ -1138,16 +1113,45 @@ wxFFile* FileAccessHelper::Open( const wxString& filename, const wxString& mode } m_file = new wxFFile( filename, mode ); - m_filename = filename; + m_entry = fileRef->entry; m_mode = mode; + + if ( writeMetadata ) { + const MemoryCardFileEntry* const entry = fileRef->entry; + + // write metadata of file if it's nonstandard + fn.AppendDir( L"_pcsx2_meta" ); + if ( entry->entry.data.mode != MemoryCardFileEntry::DefaultFileMode || entry->entry.data.attr != 0 ) { + if ( !fn.DirExists() ) { + fn.Mkdir(); + } + wxFFile metaFile( fn.GetFullPath(), L"wb" ); + if ( metaFile.IsOpened() ) { + metaFile.Write( entry->entry.raw, 0x40 ); + metaFile.Close(); + } + } else { + // if metadata is standard remove metadata file if it exists + if ( fn.FileExists() ) { + wxRemoveFile( fn.GetFullPath() ); + + // and remove the metadata dir if it's now empty + wxDir metaDir( fn.GetPath() ); + if ( metaDir.IsOpened() && !metaDir.HasFiles() ) { + wxRmdir( fn.GetPath() ); + } + } + } + } + return m_file; } -wxFFile* FileAccessHelper::ReOpen( const wxString& filename, const wxString& mode ) { - if ( m_file && mode == m_mode && filename == m_filename ) { +wxFFile* FileAccessHelper::ReOpen( const wxFileName& folderName, MemoryCardFileMetadataReference* fileRef, const wxString& mode, bool writeMetadata ) { + if ( m_file && fileRef->entry == m_entry && mode == m_mode ) { return m_file; } else { - return this->Open( filename, mode ); + return this->Open( folderName, fileRef, mode, writeMetadata ); } } diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 1076142e3..42fa3e685 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -150,7 +150,7 @@ struct MemoryCardFileMetadataReference { class FileAccessHelper { protected: wxFFile* m_file; - wxString m_filename; + const MemoryCardFileEntry* m_entry; wxString m_mode; public: @@ -158,13 +158,13 @@ public: ~FileAccessHelper(); // Get an already opened file if possible, or open a new one and remember it - wxFFile* ReOpen( const wxString& filename, const wxString& mode ); + wxFFile* ReOpen( const wxFileName& folderName, MemoryCardFileMetadataReference* fileRef, const wxString& mode, bool writeMetadata = false ); // Close an open file, if any void Close(); protected: // Open a new file and remember it for later - wxFFile* Open( const wxString& filename, const wxString& mode ); + wxFFile* Open( const wxFileName& folderName, MemoryCardFileMetadataReference* fileRef, const wxString& mode, bool writeMetadata = false ); }; // -------------------------------------------------------------------------------------- From 92c794f03b466d96538bbce2080578620f510e6e Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Thu, 11 Jun 2015 17:42:38 +0200 Subject: [PATCH 25/44] FolderMemoryCard: Fix bug that could cause crashes on memory cards that have leftover data from older saves in their file entry clusters. --- pcsx2/gui/MemoryCardFolder.cpp | 33 ++++++++++++++++++++++++--------- pcsx2/gui/MemoryCardFolder.h | 11 ++++++++--- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 647672921..9f66bc91a 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -523,7 +523,7 @@ u8* FolderMemoryCard::GetSystemBlockPointer( const u32 adr ) { if ( ( m_fat.data[0][0][fatCluster] & 0x80000000 ) == 0 ) { return nullptr; } - return GetFileEntryPointer( m_superBlock.data.rootdir_cluster, fatCluster, page % 2, offset ); + return GetFileEntryPointer( fatCluster, page % 2, offset ); } u8* src = nullptr; @@ -556,25 +556,40 @@ u8* FolderMemoryCard::GetSystemBlockPointer( const u32 adr ) { return src; } -u8* FolderMemoryCard::GetFileEntryPointer( const u32 currentCluster, const u32 searchCluster, const u32 entryNumber, const u32 offset ) { +u8* FolderMemoryCard::GetFileEntryPointer( const u32 searchCluster, const u32 entryNumber, const u32 offset ) { + const u32 fileCount = m_fileEntryDict[m_superBlock.data.rootdir_cluster].entries[0].entry.data.length; + MemoryCardFileEntryCluster* ptr = GetFileEntryCluster( m_superBlock.data.rootdir_cluster, searchCluster, fileCount ); + if ( ptr != nullptr ) { + return &ptr->entries[entryNumber].entry.raw[offset]; + } + + return nullptr; +} + +MemoryCardFileEntryCluster* FolderMemoryCard::GetFileEntryCluster( const u32 currentCluster, const u32 searchCluster, const u32 fileCount ) { // we found the correct cluster, return pointer to it if ( currentCluster == searchCluster ) { - return &m_fileEntryDict[currentCluster].entries[entryNumber].entry.raw[offset]; + return &m_fileEntryDict[currentCluster]; } // check other clusters of this directory const u32 nextCluster = m_fat.data[0][0][currentCluster] & 0x7FFFFFFF; if ( nextCluster != 0x7FFFFFFF ) { - u8* ptr = GetFileEntryPointer( nextCluster, searchCluster, entryNumber, offset ); + MemoryCardFileEntryCluster* ptr = GetFileEntryCluster( nextCluster, searchCluster, fileCount - 2 ); if ( ptr != nullptr ) { return ptr; } } // check subdirectories - for ( int i = 0; i < 2; ++i ) { - MemoryCardFileEntry* const entry = &m_fileEntryDict[currentCluster].entries[i]; - if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() && entry->entry.data.cluster != 0 ) { - u8* ptr = GetFileEntryPointer( entry->entry.data.cluster, searchCluster, entryNumber, offset ); - if ( ptr != nullptr ) { return ptr; } + auto it = m_fileEntryDict.find( currentCluster ); + if ( it != m_fileEntryDict.end() ) { + const u32 filesInThisCluster = std::min( fileCount, 2u ); + for ( unsigned int i = 0; i < filesInThisCluster; ++i ) { + MemoryCardFileEntry* const entry = &it->second.entries[i]; + if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() && entry->entry.data.cluster != 0 ) { + const u32 newFileCount = entry->entry.data.length; + MemoryCardFileEntryCluster* ptr = GetFileEntryCluster( entry->entry.data.cluster, searchCluster, newFileCount ); + if ( ptr != nullptr ) { return ptr; } + } } } diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 42fa3e685..53a6aaaef 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -280,12 +280,17 @@ protected: // returns in-memory address of file or directory metadata searchCluster corresponds to // returns nullptr if searchCluster contains something else - // originally call by passing: - // - currentCluster: the root directory cluster as indicated in the superblock // - searchCluster: the cluster that is being accessed, relative to alloc_offset in the superblock // - entryNumber: page of cluster // - offset: offset of page - u8* GetFileEntryPointer( const u32 currentCluster, const u32 searchCluster, const u32 entryNumber, const u32 offset ); + u8* GetFileEntryPointer( const u32 searchCluster, const u32 entryNumber, const u32 offset ); + + // used by GetFileEntryPointer to find the correct cluster + // returns nullptr if searchCluster is not a file or directory metadata cluster + // - currentCluster: the cluster we're currently traversing + // - searchCluster: the cluster we want + // - fileCount: the number of files left in the directory currently traversed + MemoryCardFileEntryCluster* GetFileEntryCluster( const u32 currentCluster, const u32 searchCluster, const u32 fileCount ); // returns file entry of the file at the given searchCluster // the passed fileName will be filled with a path to the file being accessed From 50ad3a8bf5a977f9f0b37df6f46454985849b684 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Thu, 11 Jun 2015 22:04:11 +0200 Subject: [PATCH 26/44] FolderMemoryCard: Clean PS2 filenames that would be illegal in Windows and write the actual names into the metadata files. This fixes issues with game such as Rayman Revolution, http://forums.pcsx2.net/Thread-New-feature-Needs-testing-Automatically-managed-Folder-Memory-Card-Public-Test?pid=463482#pid463482 --- pcsx2/gui/MemoryCardFolder.cpp | 67 +++++++++++++++++++++++++--------- pcsx2/gui/MemoryCardFolder.h | 7 +++- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 9f66bc91a..1b90d8f13 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -409,24 +409,27 @@ bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxStr fileInfo.GetTimes( NULL, &modificationTime, &creationTime ); // set file entry metadata - memset( &newFileEntry->entry.raw[0], 0x00, 0x200 ); + memset( &newFileEntry->entry.raw[0], 0x00, sizeof( newFileEntry->entry.raw ) ); wxFileName metaFileName( dirPath, fileName ); metaFileName.AppendDir( L"_pcsx2_meta" ); wxFFile metaFile; if ( metaFileName.FileExists() && metaFile.Open( metaFileName.GetFullPath(), L"rb" ) ) { - metaFile.Read( &newFileEntry->entry.raw, 0x40 ); + size_t bytesRead = metaFile.Read( &newFileEntry->entry.raw, sizeof( newFileEntry->entry.raw ) ); metaFile.Close(); + if ( bytesRead < 0x60 ) { + strcpy( (char*)&newFileEntry->entry.data.name[0], fileName.mbc_str() ); + } } else { newFileEntry->entry.data.mode = MemoryCardFileEntry::DefaultFileMode; newFileEntry->entry.data.timeCreated = MemoryCardFileEntryDateTime::FromWxDateTime( creationTime ); newFileEntry->entry.data.timeModified = MemoryCardFileEntryDateTime::FromWxDateTime( modificationTime ); + strcpy( (char*)&newFileEntry->entry.data.name[0], fileName.mbc_str() ); } newFileEntry->entry.data.length = filesize; u32 fileDataStartingCluster = GetFreeDataCluster(); newFileEntry->entry.data.cluster = fileDataStartingCluster; - strcpy( (char*)&newFileEntry->entry.data.name[0], fileName.mbc_str() ); // mark the appropriate amount of clusters as used u32 dataCluster = fileDataStartingCluster; @@ -478,18 +481,6 @@ void FolderMemoryCard::AddFileEntryToMetadataQuickAccess( MemoryCardFileEntry* c } while ( ( fileCluster = m_fat.data[0][0][fileCluster] ) != 0xFFFFFFFFu ); } -void MemoryCardFileMetadataReference::GetPath( wxFileName* fileName ) { - if ( parent ) { - parent->GetPath( fileName ); - } - - if ( entry->IsDir() ) { - fileName->AppendDir( wxString::FromAscii( (const char*)entry->entry.data.name ) ); - } else if ( entry->IsFile() ) { - fileName->SetName( wxString::FromAscii( (const char*)entry->entry.data.name ) ); - } -} - s32 FolderMemoryCard::IsPresent() { return m_isEnabled; } @@ -1116,7 +1107,7 @@ wxFFile* FileAccessHelper::Open( const wxFileName& folderName, MemoryCardFileMet this->Close(); wxFileName fn( folderName ); - fileRef->GetPath( &fn ); + bool cleanedFilename = fileRef->GetPath( &fn ); wxString filename( fn.GetFullPath() ); if ( !fn.FileExists() ) { @@ -1136,13 +1127,13 @@ wxFFile* FileAccessHelper::Open( const wxFileName& folderName, MemoryCardFileMet // write metadata of file if it's nonstandard fn.AppendDir( L"_pcsx2_meta" ); - if ( entry->entry.data.mode != MemoryCardFileEntry::DefaultFileMode || entry->entry.data.attr != 0 ) { + if ( cleanedFilename || entry->entry.data.mode != MemoryCardFileEntry::DefaultFileMode || entry->entry.data.attr != 0 ) { if ( !fn.DirExists() ) { fn.Mkdir(); } wxFFile metaFile( fn.GetFullPath(), L"wb" ); if ( metaFile.IsOpened() ) { - metaFile.Write( entry->entry.raw, 0x40 ); + metaFile.Write( entry->entry.raw, sizeof( entry->entry.raw ) ); metaFile.Close(); } } else { @@ -1178,6 +1169,46 @@ void FileAccessHelper::Close() { } } +bool FileAccessHelper::CleanMemcardFilename( char* name ) { + // invalid characters for filenames in the PS2 file system: { '/', '?', '*' } + // the following characters are valid in a PS2 memcard file system but invalid in Windows + // there's less restrictions on Linux but by cleaning them always we keep the folders cross-compatible + const char illegalChars[] = { '\\', '%', ':', '|', '"', '<', '>' }; + bool cleaned = false; + + for ( int i = 0; i < sizeof( illegalChars ); ++i ) { + // this sizeof looks really odd but I couldn't get MemoryCardFileEntry::entry.data.name (or variants) working, feel free to replace with something equivalent but nicer looking + for ( int j = 0; j < sizeof( ( (MemoryCardFileEntry*)0 )->entry.data.name ); ++j ) { + if ( name[j] == illegalChars[i] ) { + name[j] = '_'; + cleaned = true; + } + } + } + + return cleaned; +} + + +bool MemoryCardFileMetadataReference::GetPath( wxFileName* fileName ) { + bool parentCleaned = false; + if ( parent ) { + parentCleaned = parent->GetPath( fileName ); + } + + char cleanName[sizeof( entry->entry.data.name )]; + memcpy( cleanName, (const char*)entry->entry.data.name, sizeof( cleanName ) ); + bool localCleaned = FileAccessHelper::CleanMemcardFilename( cleanName ); + + if ( entry->IsDir() ) { + fileName->AppendDir( wxString::FromAscii( cleanName ) ); + } else if ( entry->IsFile() ) { + fileName->SetName( wxString::FromAscii( cleanName ) ); + } + + return parentCleaned || localCleaned; +} + FolderMemoryCardAggregator::FolderMemoryCardAggregator() { for ( uint i = 0; i < TotalCardSlots; ++i ) { diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 53a6aaaef..ba80018cc 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -140,7 +140,8 @@ struct MemoryCardFileMetadataReference { MemoryCardFileEntry* entry; u32 consecutiveCluster; - void GetPath( wxFileName* fileName ); + // returns true if filename was modified and metadata containing the actual filename should be written + bool GetPath( wxFileName* fileName ); }; // -------------------------------------------------------------------------------------- @@ -162,6 +163,10 @@ public: // Close an open file, if any void Close(); + // removes characters from a PS2 file name that would be illegal in a Windows file system + // returns true if any changes were made + static bool CleanMemcardFilename( char* name ); + protected: // Open a new file and remember it for later wxFFile* Open( const wxFileName& folderName, MemoryCardFileMetadataReference* fileRef, const wxString& mode, bool writeMetadata = false ); From 0976e124e80d9694088429e9848ee002bda3cfbd Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Thu, 11 Jun 2015 23:06:50 +0200 Subject: [PATCH 27/44] FolderMemoryCard: More robust way of checking validity of a subdirectory. Fixes this memory card: http://forums.pcsx2.net/Thread-New-feature-Needs-testing-Automatically-managed-Folder-Memory-Card-Public-Test?pid=463506#pid463506 Presumably related to something LaunchElf writes into the memory card file entries. --- pcsx2/gui/MemoryCardFolder.cpp | 9 ++++----- pcsx2/gui/MemoryCardFolder.h | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 1b90d8f13..6a7bb84f7 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -576,7 +576,7 @@ MemoryCardFileEntryCluster* FolderMemoryCard::GetFileEntryCluster( const u32 cur const u32 filesInThisCluster = std::min( fileCount, 2u ); for ( unsigned int i = 0; i < filesInThisCluster; ++i ) { MemoryCardFileEntry* const entry = &it->second.entries[i]; - if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() && entry->entry.data.cluster != 0 ) { + if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() && !entry->IsDotDir() ) { const u32 newFileCount = entry->entry.data.length; MemoryCardFileEntryCluster* ptr = GetFileEntryCluster( entry->entry.data.cluster, searchCluster, newFileCount ); if ( ptr != nullptr ) { return ptr; } @@ -618,7 +618,7 @@ MemoryCardFileEntry* FolderMemoryCard::GetFileEntryFromFileDataCluster( const u3 // check subdirectories for ( int i = 0; i < 2; ++i ) { MemoryCardFileEntry* const entry = &m_fileEntryDict[currentCluster].entries[i]; - if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() && entry->entry.data.cluster != 0 ) { + if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() && !entry->IsDotDir() ) { MemoryCardFileEntry* ptr = GetFileEntryFromFileDataCluster( entry->entry.data.cluster, searchCluster, fileName, originalDirCount, outClusterNumber ); if ( ptr != nullptr ) { fileName->InsertDir( originalDirCount, wxString::FromAscii( (const char*)entry->entry.data.name ) ); @@ -863,8 +863,7 @@ void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remaini for ( unsigned int i = 0; i < filesInThisCluster; ++i ) { MemoryCardFileEntry* entry = &entries->entries[i]; if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() ) { - const u32 cluster = entry->entry.data.cluster; - if ( cluster > 0 ) { + if ( !entry->IsDotDir() ) { const wxString subDirName = wxString::FromAscii( (const char*)entry->entry.data.name ); const wxString subDirPath = dirPath + L"/" + subDirName; @@ -888,7 +887,7 @@ void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remaini MemoryCardFileMetadataReference* dirRef = AddDirEntryToMetadataQuickAccess( entry, parent ); - FlushFileEntries( cluster, entry->entry.data.length, subDirPath, dirRef ); + FlushFileEntries( entry->entry.data.cluster, entry->entry.data.length, subDirPath, dirRef ); } } else if ( entry->IsValid() && entry->IsUsed() && entry->IsFile() ) { AddFileEntryToMetadataQuickAccess( entry, parent ); diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index ba80018cc..96e4bb338 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -112,6 +112,8 @@ struct MemoryCardFileEntry { bool IsDir() { return !!( entry.data.mode & 0x0020 ); } bool IsUsed() { return !!( entry.data.mode & 0x8000 ); } bool IsValid() { return entry.data.mode != 0xFFFFFFFF; } + // checks if we're either "." or ".." + bool IsDotDir() { return entry.data.name[0] == '.' && ( entry.data.name[1] == '\0' || ( entry.data.name[1] == '.' && entry.data.name[2] == '\0' ) ); } static const u32 DefaultDirMode = 0x8427; static const u32 DefaultFileMode = 0x8497; From af3dfd39b02ab41f2b80482243dac65ff3fb13b8 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Thu, 11 Jun 2015 23:22:49 +0200 Subject: [PATCH 28/44] FolderMemoryCard: Create directories recursively. --- pcsx2/gui/MemoryCardFolder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 6a7bb84f7..b00d2a0a7 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -1111,7 +1111,7 @@ wxFFile* FileAccessHelper::Open( const wxFileName& folderName, MemoryCardFileMet if ( !fn.FileExists() ) { if ( !fn.DirExists() ) { - fn.Mkdir(); + fn.Mkdir( 0777, wxPATH_MKDIR_FULL ); } wxFFile createEmptyFile( filename, L"wb" ); createEmptyFile.Close(); From e774011d7151507e02aac3425d097758f4440b6e Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 12 Jun 2015 00:19:37 +0200 Subject: [PATCH 29/44] FolderMemoryCard: Load the network configuration file regardless of filters. --- pcsx2/gui/MemoryCardFolder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index b00d2a0a7..20cf574fe 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -287,9 +287,9 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS if ( enableFiltering ) { bool hasFilter = !filter.IsEmpty(); if ( hasFilter ) { - localFilter = L"DATA-SYSTEM/" + filter; + localFilter = L"DATA-SYSTEM/BWNETCNF/" + filter; } else { - localFilter = L"DATA-SYSTEM"; + localFilter = L"DATA-SYSTEM/BWNETCNF"; } } From c651d28cc095468581208c77105e6bf24755d69a Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 12 Jun 2015 03:16:46 +0200 Subject: [PATCH 30/44] Clear memory card ejection timeout when a game boots. This eliminates prompts at the start of a game complaining about no memory card being inserted. I'm honestly not entirely sure if this is safe (is there some memory card driver that could cache results between different executables?) but if it isn't we'll see it soon enough! --- pcsx2/Sio.cpp | 7 +++++++ pcsx2/Sio.h | 1 + pcsx2/gui/AppCoreThread.cpp | 1 + 3 files changed, 9 insertions(+) diff --git a/pcsx2/Sio.cpp b/pcsx2/Sio.cpp index 54fe014b1..fa9f10a78 100644 --- a/pcsx2/Sio.cpp +++ b/pcsx2/Sio.cpp @@ -60,6 +60,13 @@ void SetForceMcdEjectTimeoutNow() mcds[port][slot].ForceEjection_Timeout = FORCED_MCD_EJECTION_MAX_TRIES; } +void ClearMcdEjectTimeoutNow() +{ + for( u8 port=0; port<2; ++port ) + for( u8 slot=0; slot<4; ++slot ) + mcds[port][slot].ForceEjection_Timeout = 0; +} + // SIO Inline'd IRQs : Calls the SIO interrupt handlers directly instead of // feeding them through the IOP's branch test. (see SIO.H for details) diff --git a/pcsx2/Sio.h b/pcsx2/Sio.h index 192545fad..512fc3009 100644 --- a/pcsx2/Sio.h +++ b/pcsx2/Sio.h @@ -127,5 +127,6 @@ extern void sioWriteCtrl16(u16 value); extern void sioInterrupt(); extern void InitializeSIO(u8 value); extern void SetForceMcdEjectTimeoutNow(); +extern void ClearMcdEjectTimeoutNow(); extern void sioNextFrame(); extern void sioSetGameSerial(const wxString& serial); diff --git a/pcsx2/gui/AppCoreThread.cpp b/pcsx2/gui/AppCoreThread.cpp index f7b7a8688..c3c93ee24 100644 --- a/pcsx2/gui/AppCoreThread.cpp +++ b/pcsx2/gui/AppCoreThread.cpp @@ -522,6 +522,7 @@ void AppCoreThread::GameStartingInThread() m_ExecMode = ExecMode_Paused; OnResumeReady(); _reset_stuff_as_needed(); + ClearMcdEjectTimeoutNow(); // probably safe to do this when a game boots, eliminates annoying prompts m_ExecMode = ExecMode_Opened; _parent::GameStartingInThread(); From eab49dbfa306a5985e6bd7c0f4db6fc55f5adec1 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 12 Jun 2015 03:57:37 +0200 Subject: [PATCH 31/44] FolderMemoryCard: Reduce console logs. --- pcsx2/gui/MemoryCardFolder.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 20cf574fe..15c251c67 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -78,8 +78,8 @@ void FolderMemoryCard::Open( const wxString& fullPath, const AppConfig::McdOptio } } } else { - str = L"[disabled]"; - disabled = true; + // if the user has disabled this slot or is using a different memory card type, just return without a console log + return; } Console.WriteLn( disabled ? Color_Gray : Color_Green, L"McdSlot %u: [Folder] " + str, m_slot ); @@ -276,8 +276,6 @@ bool FilterMatches( const wxString& fileName, const wxString& filter ) { bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, MemoryCardFileMetadataReference* parent, const bool enableFiltering, const wxString& filter ) { wxDir dir( dirPath ); if ( dir.IsOpened() ) { - Console.WriteLn( L"(FolderMcd) Adding folder: %s", WX_STR( dirPath ) ); - const u32 dirStartCluster = dirEntry->entry.data.cluster; wxString fileName; @@ -388,7 +386,6 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName, MemoryCardFileMetadataReference* parent ) { wxFileName relativeFilePath( dirPath, fileName ); relativeFilePath.MakeRelativeTo( m_folderName.GetPath() ); - Console.WriteLn( L"(FolderMcd) Adding file: %s", WX_STR( relativeFilePath.GetFullPath() ) ); wxFileName fileInfo( dirPath, fileName ); wxFFile file( fileInfo.GetFullPath(), L"rb" ); From a886f6e7e1eb5031ab4613d979f0b520404fe6f9 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 27 Jun 2015 21:35:17 +0200 Subject: [PATCH 32/44] Fix some gcc warnings. --- pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp | 3 +++ pcsx2/gui/MemoryCardFolder.cpp | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp b/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp index 9c2766794..0185cdc5b 100644 --- a/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp +++ b/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp @@ -119,6 +119,9 @@ void Dialogs::ConvertMemoryCardDialog::OnOk_Click( wxCommandEvent& evt ) { case MemoryCardType::MemoryCard_Folder: success = ConvertToFolder( sourcePath, targetPath ); break; + default: + Msgbox::Alert( _( "This target type is not supported!" ), _( "Convert memory card" ) ); + return; } } diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 15c251c67..a0c125a21 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -1172,9 +1172,9 @@ bool FileAccessHelper::CleanMemcardFilename( char* name ) { const char illegalChars[] = { '\\', '%', ':', '|', '"', '<', '>' }; bool cleaned = false; - for ( int i = 0; i < sizeof( illegalChars ); ++i ) { + for ( size_t i = 0; i < sizeof( illegalChars ); ++i ) { // this sizeof looks really odd but I couldn't get MemoryCardFileEntry::entry.data.name (or variants) working, feel free to replace with something equivalent but nicer looking - for ( int j = 0; j < sizeof( ( (MemoryCardFileEntry*)0 )->entry.data.name ); ++j ) { + for ( size_t j = 0; j < sizeof( ( (MemoryCardFileEntry*)0 )->entry.data.name ); ++j ) { if ( name[j] == illegalChars[i] ) { name[j] = '_'; cleaned = true; From 40e6a5c515e02aed41a438d50255b83684e72171 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 27 Jun 2015 22:06:10 +0200 Subject: [PATCH 33/44] FolderMemoryCard: Don't assume that C limits are defined. --- pcsx2/gui/MemoryCardFolder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index a0c125a21..b33074f27 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -1012,7 +1012,7 @@ void FolderMemoryCard::SetSlot( uint slot ) { u32 FolderMemoryCard::GetSizeInClusters() { const u32 clusters = m_superBlock.data.clusters_per_card; - if ( clusters > 0 && clusters < UINT32_MAX ) { + if ( clusters > 0 && clusters < 0xFFFFFFFFu ) { return clusters; } else { return TotalClusters; From 65058df3da2e7d953389984f5de5433343372ed0 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 29 Jun 2015 05:08:27 +0200 Subject: [PATCH 34/44] Move the CopyDirectory() and RemoveDirectory() functions into FileUtils.cpp. --- pcsx2/Utilities/FileUtils.cpp | 70 ++++++++++++++++++++++++ pcsx2/gui/Panels/MemoryCardListPanel.cpp | 70 +----------------------- 2 files changed, 73 insertions(+), 67 deletions(-) diff --git a/pcsx2/Utilities/FileUtils.cpp b/pcsx2/Utilities/FileUtils.cpp index 5da4ecdf3..739635d6a 100644 --- a/pcsx2/Utilities/FileUtils.cpp +++ b/pcsx2/Utilities/FileUtils.cpp @@ -16,6 +16,9 @@ #include "PrecompiledHeader.h" #include "AsciiFile.h" +#include +#include + void AsciiFile::Printf( const char* fmt, ... ) { va_list list; @@ -25,3 +28,70 @@ void AsciiFile::Printf( const char* fmt, ... ) va_end( list ); Write( ascii, strlen(ascii) ); } + +bool CopyDirectory( const wxString& from, const wxString& to ) { + wxDir src( from ); + if ( !src.IsOpened() ) { + return false; + } + + wxMkdir( to ); + wxDir dst( to ); + if ( !dst.IsOpened() ) { + return false; + } + + wxString filename; + + // copy directories + if ( src.GetFirst( &filename, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN ) ) { + do { + if ( !CopyDirectory( wxFileName( from, filename ).GetFullPath(), wxFileName( to, filename ).GetFullPath() ) ) { + return false; + } + } while ( src.GetNext( &filename ) ); + } + + // copy files + if ( src.GetFirst( &filename, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN ) ) { + do { + if ( !wxCopyFile( wxFileName( from, filename ).GetFullPath(), wxFileName( to, filename ).GetFullPath() ) ) { + return false; + } + } while ( src.GetNext( &filename ) ); + } + + return true; +} + +bool RemoveDirectory( const wxString& dirname ) { + { + wxDir dir( dirname ); + if ( !dir.IsOpened() ) { + return false; + } + + wxString filename; + + // delete subdirs recursively + if ( dir.GetFirst( &filename, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN ) ) { + do { + if ( !RemoveDirectory( wxFileName( dirname, filename ).GetFullPath() ) ) { + return false; + } + } while ( dir.GetNext( &filename ) ); + } + + // delete files + if ( dir.GetFirst( &filename, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN ) ) { + do { + if ( !wxRemoveFile( wxFileName( dirname, filename ).GetFullPath() ) ) { + return false; + } + } while ( dir.GetNext( &filename ) ); + } + } + + // oddly enough this has different results compared to the more sensible dirname.Rmdir(), don't change! + return wxFileName::Rmdir( dirname ); +} diff --git a/pcsx2/gui/Panels/MemoryCardListPanel.cpp b/pcsx2/gui/Panels/MemoryCardListPanel.cpp index d391088bc..75bd3f5ae 100644 --- a/pcsx2/gui/Panels/MemoryCardListPanel.cpp +++ b/pcsx2/gui/Panels/MemoryCardListPanel.cpp @@ -30,6 +30,9 @@ #include +bool CopyDirectory( const wxString& from, const wxString& to ); +bool RemoveDirectory( const wxString& dirname ); + using namespace pxSizerFlags; using namespace Panels; @@ -779,73 +782,6 @@ void Panels::MemoryCardListPanel_Simple::UiConvertCard( McdSlotItem& card ) { closed_core.AllowResume(); } -bool CopyDirectory( const wxString& from, const wxString& to ) { - wxDir src( from ); - if ( !src.IsOpened() ) { - return false; - } - - wxMkdir( to ); - wxDir dst( to ); - if ( !dst.IsOpened() ) { - return false; - } - - wxString filename; - - // copy directories - if ( src.GetFirst( &filename, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN ) ) { - do { - if ( !CopyDirectory( wxFileName( from, filename ).GetFullPath(), wxFileName( to, filename ).GetFullPath() ) ) { - return false; - } - } while ( src.GetNext( &filename ) ); - } - - // copy files - if ( src.GetFirst( &filename, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN ) ) { - do { - if ( !wxCopyFile( wxFileName( from, filename ).GetFullPath(), wxFileName( to, filename ).GetFullPath() ) ) { - return false; - } - } while ( src.GetNext( &filename ) ); - } - - return true; -} - -bool RemoveDirectory( const wxString& dirname ) { - { - wxDir dir( dirname ); - if ( !dir.IsOpened() ) { - return false; - } - - wxString filename; - - // delete subdirs recursively - if ( dir.GetFirst( &filename, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN ) ) { - do { - if ( !RemoveDirectory( wxFileName( dirname, filename ).GetFullPath() ) ) { - return false; - } - } while ( dir.GetNext( &filename ) ); - } - - // delete files - if ( dir.GetFirst( &filename, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN ) ) { - do { - if ( !wxRemoveFile( wxFileName( dirname, filename ).GetFullPath() ) ) { - return false; - } - } while ( dir.GetNext( &filename ) ); - } - } - - // oddly enough this has different results compared to the more sensible dirname.Rmdir(), don't change! - return wxFileName::Rmdir( dirname ); -} - void Panels::MemoryCardListPanel_Simple::UiDeleteCard( McdSlotItem& card ) { if( !card.IsPresent ){ From fbc8d30b67556ec96285fc0ab96f8fc0034d3fde Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 29 Jun 2015 16:20:32 +0200 Subject: [PATCH 35/44] FolderMemoryCard: Cleaned filenames should be used for directories, as well. --- pcsx2/gui/MemoryCardFolder.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index b33074f27..2bd996914 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -339,18 +339,20 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS metaFileName.AppendDir( fileName ); wxFFile metaFile; if ( metaFileName.FileExists() && metaFile.Open( metaFileName.GetFullPath(), L"rb" ) ) { - metaFile.Read( &newDirEntry->entry.raw, 0x40 ); + size_t bytesRead = metaFile.Read( &newDirEntry->entry.raw, sizeof( newDirEntry->entry.raw ) ); metaFile.Close(); + if ( bytesRead < 0x60 ) { + strcpy( (char*)&newDirEntry->entry.data.name[0], fileName.mbc_str() ); + } } else { newDirEntry->entry.data.mode = MemoryCardFileEntry::DefaultDirMode; newDirEntry->entry.data.timeCreated = MemoryCardFileEntryDateTime::FromWxDateTime( creationTime ); newDirEntry->entry.data.timeModified = MemoryCardFileEntryDateTime::FromWxDateTime( modificationTime ); + strcpy( (char*)&newDirEntry->entry.data.name[0], fileName.mbc_str() ); } - newDirEntry->entry.data.length = 2; - strcpy( (char*)&newDirEntry->entry.data.name[0], fileName.mbc_str() ); - // create new cluster for . and .. entries + newDirEntry->entry.data.length = 2; u32 newCluster = GetFreeDataCluster(); m_fat.data[0][0][newCluster] = 0xFFFFFFFF; newDirEntry->entry.data.cluster = newCluster; @@ -861,18 +863,21 @@ void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remaini MemoryCardFileEntry* entry = &entries->entries[i]; if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() ) { if ( !entry->IsDotDir() ) { - const wxString subDirName = wxString::FromAscii( (const char*)entry->entry.data.name ); + char cleanName[sizeof( entry->entry.data.name )]; + memcpy( cleanName, (const char*)entry->entry.data.name, sizeof( cleanName ) ); + bool filenameCleaned = FileAccessHelper::CleanMemcardFilename( cleanName ); + const wxString subDirName = wxString::FromAscii( (const char*)cleanName ); const wxString subDirPath = dirPath + L"/" + subDirName; // if this directory has nonstandard metadata, write that to the file system wxFileName metaFileName( m_folderName.GetFullPath() + subDirPath + L"/_pcsx2_meta_directory" ); - if ( entry->entry.data.mode != MemoryCardFileEntry::DefaultDirMode || entry->entry.data.attr != 0 ) { + if ( filenameCleaned || entry->entry.data.mode != MemoryCardFileEntry::DefaultDirMode || entry->entry.data.attr != 0 ) { if ( !metaFileName.DirExists() ) { metaFileName.Mkdir(); } wxFFile metaFile( metaFileName.GetFullPath(), L"wb" ); if ( metaFile.IsOpened() ) { - metaFile.Write( entry->entry.raw, 0x40 ); + metaFile.Write( entry->entry.raw, sizeof( entry->entry.raw ) ); metaFile.Close(); } } else { From c4570750ea3ef2b7336b47248332ada01c494fd6 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 29 Jun 2015 00:29:07 +0200 Subject: [PATCH 36/44] FolderMemoryCard: Put the initialization of the file entry flushing logic into its own method. --- pcsx2/gui/MemoryCardFolder.cpp | 17 +++++++++++------ pcsx2/gui/MemoryCardFolder.h | 3 +++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 2bd996914..7c13ed8a9 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -813,12 +813,7 @@ void FolderMemoryCard::Flush() { } // then all directory and file entries - const u32 rootDirCluster = m_superBlock.data.rootdir_cluster; - FlushCluster( rootDirCluster + m_superBlock.data.alloc_offset ); - MemoryCardFileEntryCluster* rootEntries = &m_fileEntryDict[rootDirCluster]; - if ( rootEntries->entries[0].IsValid() && rootEntries->entries[0].IsUsed() ) { - FlushFileEntries( rootDirCluster, rootEntries->entries[0].entry.data.length ); - } + FlushFileEntries(); // and finally, flush everything that hasn't been flushed yet for ( uint i = 0; i < pageCount; ++i ) { @@ -852,6 +847,16 @@ void FolderMemoryCard::FlushBlock( const u32 block ) { } } +void FolderMemoryCard::FlushFileEntries() { + // Flush all file entry data from the cache into m_fileEntryDict. + const u32 rootDirCluster = m_superBlock.data.rootdir_cluster; + FlushCluster( rootDirCluster + m_superBlock.data.alloc_offset ); + MemoryCardFileEntryCluster* rootEntries = &m_fileEntryDict[rootDirCluster]; + if ( rootEntries->entries[0].IsValid() && rootEntries->entries[0].IsUsed() ) { + FlushFileEntries( rootDirCluster, rootEntries->entries[0].entry.data.length ); + } +} + void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remainingFiles, const wxString& dirPath, MemoryCardFileMetadataReference* parent ) { // flush the current cluster FlushCluster( dirCluster + m_superBlock.data.alloc_offset ); diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 96e4bb338..e30d20f9d 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -381,6 +381,9 @@ protected: // flush a whole memory card block of the cache to the internal data and/or host file system void FlushBlock( const u32 block ); + // flush all directory and file entries to the internal data + void FlushFileEntries(); + // 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 ); From c0cc91fd0a9a780ddd2c70e83263e3cfcb65a56e Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Tue, 30 Jun 2015 03:33:32 +0200 Subject: [PATCH 37/44] FolderMemoryCard: Only add folders if all files and subfolders in it also fit into the remaining memory card space. This avoids situations where, for example, it would only add the icon file but not the game data file because the memory card was near-full. --- pcsx2/gui/MemoryCardFolder.cpp | 35 +++++++++++++++++++++++++++++++++- pcsx2/gui/MemoryCardFolder.h | 3 +++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 7c13ed8a9..a4fcb7f79 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -316,7 +316,7 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS } // make sure we have enough space on the memcard for the directory - const u32 newNeededClusters = ( dirEntry->entry.data.length % 2 ) == 0 ? 2 : 1; + const u32 newNeededClusters = CalculateRequiredClustersOfDirectory( dirPath + L"/" + fileName ) + ( ( dirEntry->entry.data.length % 2 ) == 0 ? 1 : 0 ); if ( newNeededClusters > GetAmountFreeDataClusters() ) { Console.Warning( GetCardFullMessage( fileName ) ); hasNext = dir.GetNext( &fileName ); @@ -454,6 +454,39 @@ bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxStr return true; } +u32 FolderMemoryCard::CalculateRequiredClustersOfDirectory( const wxString& dirPath ) { + const u32 clusterSize = m_superBlock.data.pages_per_cluster * m_superBlock.data.page_len; + u32 requiredFileEntryPages = 2; + u32 requiredClusters = 0; + + wxDir dir( dirPath ); + wxString fileName; + bool hasNext = dir.GetFirst( &fileName ); + while ( hasNext ) { + if ( fileName.StartsWith( L"_pcsx2_" ) ) { + hasNext = dir.GetNext( &fileName ); + continue; + } + + ++requiredFileEntryPages; + wxFileName file( dirPath, fileName ); + wxString path = file.GetFullPath(); + bool isFile = wxFile::Exists( path ); + + if ( isFile ) { + const u32 filesize = file.GetSize().GetValue(); + const u32 countClusters = ( filesize % clusterSize ) != 0 ? ( filesize / clusterSize + 1 ) : ( filesize / clusterSize ); + requiredClusters += countClusters; + } else { + requiredClusters += CalculateRequiredClustersOfDirectory( path ); + } + + hasNext = dir.GetNext( &fileName ); + } + + return requiredClusters + requiredFileEntryPages / 2 + ( requiredFileEntryPages % 2 == 0 ? 0 : 1 ); +} + MemoryCardFileMetadataReference* FolderMemoryCard::AddDirEntryToMetadataQuickAccess( MemoryCardFileEntry* const entry, MemoryCardFileMetadataReference* const parent ) { MemoryCardFileMetadataReference* ref = &m_fileMetadataQuickAccess[entry->entry.data.cluster]; ref->parent = parent; diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index e30d20f9d..554414380 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -357,6 +357,9 @@ protected: // - parent: pointer to the parent dir's quick-access reference element bool AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName, MemoryCardFileMetadataReference* parent = nullptr ); + // calculates the amount of clusters a directory would use up if put into a memory card + u32 CalculateRequiredClustersOfDirectory( const wxString& dirPath ); + // adds a file to the quick-access dictionary, so it can be accessed more efficiently (ie, without searching through the entire file system) later void AddFileEntryToMetadataQuickAccess( MemoryCardFileEntry* const entry, MemoryCardFileMetadataReference* const parent ); From 798ec3eb9c8244ac3dc06bb6292f213a5cabcf79 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 3 Jul 2015 04:02:26 +0200 Subject: [PATCH 38/44] FolderMemoryCard: Some code cleanup. Use more named constants, mark methods as const where appropriate, and some other minor things. --- pcsx2/gui/MemoryCardFolder.cpp | 131 +++++++++++++++++---------------- pcsx2/gui/MemoryCardFolder.h | 67 ++++++++++++----- 2 files changed, 114 insertions(+), 84 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index a4fcb7f79..101259e03 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -43,7 +43,7 @@ void FolderMemoryCard::InitializeInternalData() { m_lastAccessedFile.Close(); } -bool FolderMemoryCard::IsFormatted() { +bool FolderMemoryCard::IsFormatted() const { // this should be a good enough arbitrary check, if someone can think of a case where this doesn't work feel free to change return m_superBlock.raw[0x16] == 0x6F; } @@ -155,21 +155,23 @@ void FolderMemoryCard::CreateFat() { void FolderMemoryCard::CreateRootDir() { MemoryCardFileEntryCluster* const rootCluster = &m_fileEntryDict[m_superBlock.data.rootdir_cluster]; - memset( &rootCluster->entries[0].entry.raw[0], 0x00, 0x200 ); - rootCluster->entries[0].entry.data.mode = 0x8427; + memset( &rootCluster->entries[0].entry.raw[0], 0x00, sizeof( rootCluster->entries[0].entry.raw ) ); + rootCluster->entries[0].entry.data.mode = MemoryCardFileEntry::Mode_Read | MemoryCardFileEntry::Mode_Write | MemoryCardFileEntry::Mode_Execute + | MemoryCardFileEntry::Mode_Directory | MemoryCardFileEntry::Mode_Unknown0x0400 | MemoryCardFileEntry::Mode_Used; rootCluster->entries[0].entry.data.length = 2; rootCluster->entries[0].entry.data.name[0] = '.'; - memset( &rootCluster->entries[1].entry.raw[0], 0x00, 0x200 ); - rootCluster->entries[1].entry.data.mode = 0xA426; + memset( &rootCluster->entries[1].entry.raw[0], 0x00, sizeof( rootCluster->entries[1].entry.raw ) ); + rootCluster->entries[1].entry.data.mode = MemoryCardFileEntry::Mode_Write | MemoryCardFileEntry::Mode_Execute | MemoryCardFileEntry::Mode_Directory + | MemoryCardFileEntry::Mode_Unknown0x0400 | MemoryCardFileEntry::Mode_Unknown0x2000 | MemoryCardFileEntry::Mode_Used; rootCluster->entries[1].entry.data.name[0] = '.'; rootCluster->entries[1].entry.data.name[1] = '.'; // mark root dir cluster as used - m_fat.data[0][0][m_superBlock.data.rootdir_cluster] = 0xFFFFFFFFu; + m_fat.data[0][0][m_superBlock.data.rootdir_cluster] = LastDataCluster | DataClusterInUseMask; } -u32 FolderMemoryCard::GetFreeSystemCluster() { +u32 FolderMemoryCard::GetFreeSystemCluster() const { // first block is reserved for superblock u32 highestUsedCluster = ( m_superBlock.data.pages_per_block / m_superBlock.data.pages_per_cluster ) - 1; @@ -181,7 +183,7 @@ u32 FolderMemoryCard::GetFreeSystemCluster() { // or fat clusters for ( int i = 0; i < IndirectFatClusterCount; ++i ) { for ( int j = 0; j < ClusterSize / 4; ++j ) { - if ( m_indirectFat.data[i][j] != 0xFFFFFFFFu ) { + if ( m_indirectFat.data[i][j] != IndirectFatUnused ) { highestUsedCluster = std::max( highestUsedCluster, m_indirectFat.data[i][j] ); } } @@ -190,33 +192,37 @@ u32 FolderMemoryCard::GetFreeSystemCluster() { return highestUsedCluster + 1; } -u32 FolderMemoryCard::GetFreeDataCluster() { +u32 FolderMemoryCard::GetAmountDataClusters() const { // BIOS reports different cluster values than what the memory card actually has, match that when adding files // 8mb card -> BIOS: 7999 clusters / Superblock: 8135 clusters // 16mb card -> BIOS: 15999 clusters / Superblock: 16295 clusters // 32mb card -> BIOS: 31999 clusters / Superblock: 32615 clusters // 64mb card -> BIOS: 64999 clusters / Superblock: 65255 clusters - const u32 countDataClusters = ( m_superBlock.data.alloc_end / 1000 ) * 1000 - 1; + return ( m_superBlock.data.alloc_end / 1000 ) * 1000 - 1; +} + +u32 FolderMemoryCard::GetFreeDataCluster() const { + const u32 countDataClusters = GetAmountDataClusters(); for ( unsigned int i = 0; i < countDataClusters; ++i ) { const u32 cluster = m_fat.data[0][0][i]; - if ( ( cluster & 0x80000000 ) == 0 ) { + if ( ( cluster & DataClusterInUseMask ) == 0 ) { return i; } } - return 0xFFFFFFFF; + return 0xFFFFFFFFu; } -u32 FolderMemoryCard::GetAmountFreeDataClusters() { - const u32 countDataClusters = ( m_superBlock.data.alloc_end / 1000 ) * 1000 - 1; +u32 FolderMemoryCard::GetAmountFreeDataClusters() const { + const u32 countDataClusters = GetAmountDataClusters(); u32 countFreeDataClusters = 0; for ( unsigned int i = 0; i < countDataClusters; ++i ) { const u32 cluster = m_fat.data[0][0][i]; - if ( ( cluster & 0x80000000 ) == 0 ) { + if ( ( cluster & DataClusterInUseMask ) == 0 ) { ++countFreeDataClusters; } } @@ -224,17 +230,17 @@ u32 FolderMemoryCard::GetAmountFreeDataClusters() { return countFreeDataClusters; } -u32 FolderMemoryCard::GetLastClusterOfData( const u32 cluster ) { +u32 FolderMemoryCard::GetLastClusterOfData( const u32 cluster ) const { u32 entryCluster; u32 nextCluster = cluster; do { entryCluster = nextCluster; - nextCluster = m_fat.data[0][0][entryCluster] & 0x7FFFFFFF; - } while ( nextCluster != 0x7FFFFFFF ); + nextCluster = m_fat.data[0][0][entryCluster] & NextDataClusterMask; + } while ( nextCluster != LastDataCluster ); return entryCluster; } -MemoryCardFileEntry* FolderMemoryCard::AppendFileEntryToDir( MemoryCardFileEntry* const dirEntry ) { +MemoryCardFileEntry* FolderMemoryCard::AppendFileEntryToDir( const MemoryCardFileEntry* const dirEntry ) { u32 entryCluster = GetLastClusterOfData( dirEntry->entry.data.cluster ); MemoryCardFileEntry* newFileEntry; @@ -242,8 +248,8 @@ MemoryCardFileEntry* FolderMemoryCard::AppendFileEntryToDir( MemoryCardFileEntry // need new cluster u32 newCluster = GetFreeDataCluster(); if ( newCluster == 0xFFFFFFFFu ) { return nullptr; } - m_fat.data[0][0][entryCluster] = newCluster | 0x80000000; - m_fat.data[0][0][newCluster] = 0xFFFFFFFF; + m_fat.data[0][0][entryCluster] = newCluster | DataClusterInUseMask; + m_fat.data[0][0][newCluster] = LastDataCluster | DataClusterInUseMask; newFileEntry = &m_fileEntryDict[newCluster].entries[0]; } else { // can use last page of existing clusters @@ -354,16 +360,16 @@ bool FolderMemoryCard::AddFolder( MemoryCardFileEntry* const dirEntry, const wxS // create new cluster for . and .. entries newDirEntry->entry.data.length = 2; u32 newCluster = GetFreeDataCluster(); - m_fat.data[0][0][newCluster] = 0xFFFFFFFF; + m_fat.data[0][0][newCluster] = LastDataCluster | DataClusterInUseMask; newDirEntry->entry.data.cluster = newCluster; MemoryCardFileEntryCluster* const subDirCluster = &m_fileEntryDict[newCluster]; - memset( &subDirCluster->entries[0].entry.raw[0], 0x00, 0x200 ); + memset( &subDirCluster->entries[0].entry.raw[0], 0x00, sizeof( subDirCluster->entries[0].entry.raw ) ); subDirCluster->entries[0].entry.data.mode = MemoryCardFileEntry::DefaultDirMode; subDirCluster->entries[0].entry.data.dirEntry = entryNumber; subDirCluster->entries[0].entry.data.name[0] = '.'; - memset( &subDirCluster->entries[1].entry.raw[0], 0x00, 0x200 ); + memset( &subDirCluster->entries[1].entry.raw[0], 0x00, sizeof( subDirCluster->entries[1].entry.raw ) ); subDirCluster->entries[1].entry.data.mode = MemoryCardFileEntry::DefaultDirMode; subDirCluster->entries[1].entry.data.name[0] = '.'; subDirCluster->entries[1].entry.data.name[1] = '.'; @@ -432,29 +438,29 @@ bool FolderMemoryCard::AddFile( MemoryCardFileEntry* const dirEntry, const wxStr // mark the appropriate amount of clusters as used u32 dataCluster = fileDataStartingCluster; - m_fat.data[0][0][dataCluster] = 0xFFFFFFFF; + m_fat.data[0][0][dataCluster] = LastDataCluster | DataClusterInUseMask; for ( unsigned int i = 0; i < countClusters - 1; ++i ) { u32 newCluster = GetFreeDataCluster(); - m_fat.data[0][0][dataCluster] = newCluster | 0x80000000; - m_fat.data[0][0][newCluster] = 0xFFFFFFFF; + m_fat.data[0][0][dataCluster] = newCluster | DataClusterInUseMask; + m_fat.data[0][0][newCluster] = LastDataCluster | DataClusterInUseMask; dataCluster = newCluster; } file.Close(); AddFileEntryToMetadataQuickAccess( newFileEntry, parent ); + + // and finally, increase file count in the directory entry + dirEntry->entry.data.length++; + + return true; } else { Console.WriteLn( L"(FolderMcd) Could not open file: %s", WX_STR( relativeFilePath.GetFullPath() ) ); return false; } - - // and finally, increase file count in the directory entry - dirEntry->entry.data.length++; - - return true; } -u32 FolderMemoryCard::CalculateRequiredClustersOfDirectory( const wxString& dirPath ) { +u32 FolderMemoryCard::CalculateRequiredClustersOfDirectory( const wxString& dirPath ) const { const u32 clusterSize = m_superBlock.data.pages_per_cluster * m_superBlock.data.page_len; u32 requiredFileEntryPages = 2; u32 requiredClusters = 0; @@ -505,19 +511,19 @@ void FolderMemoryCard::AddFileEntryToMetadataQuickAccess( MemoryCardFileEntry* c u32 clusterNumber = 0; do { - MemoryCardFileMetadataReference* ref = &m_fileMetadataQuickAccess[fileCluster & 0x7FFFFFFFu]; + MemoryCardFileMetadataReference* ref = &m_fileMetadataQuickAccess[fileCluster & NextDataClusterMask]; ref->parent = parent; ref->entry = entry; ref->consecutiveCluster = clusterNumber; ++clusterNumber; - } while ( ( fileCluster = m_fat.data[0][0][fileCluster] ) != 0xFFFFFFFFu ); + } while ( ( fileCluster = m_fat.data[0][0][fileCluster] ) != ( LastDataCluster | DataClusterInUseMask ) ); } -s32 FolderMemoryCard::IsPresent() { +s32 FolderMemoryCard::IsPresent() const { return m_isEnabled; } -void FolderMemoryCard::GetSizeInfo( PS2E_McdSizeInfo& outways ) { +void FolderMemoryCard::GetSizeInfo( PS2E_McdSizeInfo& outways ) const { outways.SectorSize = PageSize; outways.EraseBlockSizeInSectors = BlockSize / PageSize; outways.McdSizeInSectors = GetSizeInClusters() * 2; @@ -527,7 +533,7 @@ void FolderMemoryCard::GetSizeInfo( PS2E_McdSizeInfo& outways ) { outways.Xor ^= pdata[0] ^ pdata[1] ^ pdata[2] ^ pdata[3]; } -bool FolderMemoryCard::IsPSX() { +bool FolderMemoryCard::IsPSX() const { return false; } @@ -543,40 +549,37 @@ u8* FolderMemoryCard::GetSystemBlockPointer( const u32 adr ) { // trying to access a file entry? const u32 fatCluster = cluster - m_superBlock.data.alloc_offset; // if this cluster is unused according to FAT, we can assume we won't find anything - if ( ( m_fat.data[0][0][fatCluster] & 0x80000000 ) == 0 ) { + if ( ( m_fat.data[0][0][fatCluster] & DataClusterInUseMask ) == 0 ) { return nullptr; } return GetFileEntryPointer( fatCluster, page % 2, offset ); } - u8* src = nullptr; if ( block == 0 ) { - src = &m_superBlock.raw[page * PageSize + offset]; + return &m_superBlock.raw[page * PageSize + offset]; } else if ( block == m_superBlock.data.backup_block1 ) { - src = &m_backupBlock1[( page % 16 ) * PageSize + offset]; + return &m_backupBlock1[( page % 16 ) * PageSize + offset]; } else if ( block == m_superBlock.data.backup_block2 ) { - src = &m_backupBlock2.raw[( page % 16 ) * PageSize + offset]; + return &m_backupBlock2.raw[( page % 16 ) * PageSize + offset]; } else { // trying to access indirect FAT? for ( int i = 0; i < IndirectFatClusterCount; ++i ) { if ( cluster == m_superBlock.data.ifc_list[i] ) { - src = &m_indirectFat.raw[i][( page % 2 ) * PageSize + offset]; - return src; + return &m_indirectFat.raw[i][( page % 2 ) * PageSize + offset]; } } // trying to access FAT? for ( int i = 0; i < IndirectFatClusterCount; ++i ) { for ( int j = 0; j < ClusterSize / 4; ++j ) { const u32 fatCluster = m_indirectFat.data[i][j]; - if ( fatCluster != 0xFFFFFFFFu && fatCluster == cluster ) { - src = &m_fat.raw[i][j][( page % 2 ) * PageSize + offset]; - return src; + if ( fatCluster != IndirectFatUnused && fatCluster == cluster ) { + return &m_fat.raw[i][j][( page % 2 ) * PageSize + offset]; } } } } - return src; + return nullptr; } u8* FolderMemoryCard::GetFileEntryPointer( const u32 searchCluster, const u32 entryNumber, const u32 offset ) { @@ -596,8 +599,8 @@ MemoryCardFileEntryCluster* FolderMemoryCard::GetFileEntryCluster( const u32 cur } // check other clusters of this directory - const u32 nextCluster = m_fat.data[0][0][currentCluster] & 0x7FFFFFFF; - if ( nextCluster != 0x7FFFFFFF ) { + const u32 nextCluster = m_fat.data[0][0][currentCluster] & NextDataClusterMask; + if ( nextCluster != LastDataCluster ) { MemoryCardFileEntryCluster* ptr = GetFileEntryCluster( nextCluster, searchCluster, fileCount - 2 ); if ( ptr != nullptr ) { return ptr; } } @@ -607,7 +610,7 @@ MemoryCardFileEntryCluster* FolderMemoryCard::GetFileEntryCluster( const u32 cur if ( it != m_fileEntryDict.end() ) { const u32 filesInThisCluster = std::min( fileCount, 2u ); for ( unsigned int i = 0; i < filesInThisCluster; ++i ) { - MemoryCardFileEntry* const entry = &it->second.entries[i]; + const MemoryCardFileEntry* const entry = &it->second.entries[i]; if ( entry->IsValid() && entry->IsUsed() && entry->IsDir() && !entry->IsDotDir() ) { const u32 newFileCount = entry->entry.data.length; MemoryCardFileEntryCluster* ptr = GetFileEntryCluster( entry->entry.data.cluster, searchCluster, newFileCount ); @@ -635,14 +638,14 @@ MemoryCardFileEntry* FolderMemoryCard::GetFileEntryFromFileDataCluster( const u3 return entry; } ++clusterNumber; - } while ( ( fileCluster = m_fat.data[0][0][fileCluster] & 0x7FFFFFFF ) != 0x7FFFFFFF ); + } while ( ( fileCluster = m_fat.data[0][0][fileCluster] & NextDataClusterMask ) != LastDataCluster ); } } // check other clusters of this directory // this can probably be solved more efficiently by looping through nextClusters instead of recursively calling - const u32 nextCluster = m_fat.data[0][0][currentCluster] & 0x7FFFFFFF; - if ( nextCluster != 0x7FFFFFFF ) { + const u32 nextCluster = m_fat.data[0][0][currentCluster] & NextDataClusterMask; + if ( nextCluster != LastDataCluster ) { MemoryCardFileEntry* ptr = GetFileEntryFromFileDataCluster( nextCluster, searchCluster, fileName, originalDirCount, outClusterNumber ); if ( ptr != nullptr ) { return ptr; } } @@ -669,7 +672,7 @@ bool FolderMemoryCard::ReadFromFile( u8 *dest, u32 adr, u32 dataLength ) { const u32 fatCluster = cluster - m_superBlock.data.alloc_offset; // if the cluster is unused according to FAT, just return - if ( ( m_fat.data[0][0][fatCluster] & 0x80000000 ) == 0 ) { + if ( ( m_fat.data[0][0][fatCluster] & DataClusterInUseMask ) == 0 ) { return false; } @@ -739,7 +742,7 @@ s32 FolderMemoryCard::Read( u8 *dest, u32 adr, int size ) { // is trying to (partially) read the ECC const u32 eccOffset = PageSize - offset; const u32 eccLength = std::min( (u32)( size - offset ), (u32)EccSize ); - const u32 adrStart = page * 0x210u; + const u32 adrStart = page * PageSizeRaw; u8 data[PageSize]; Read( data, adrStart, PageSize ); @@ -936,8 +939,8 @@ void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remaini // continue to the next cluster of this directory const u32 nextCluster = m_fat.data[0][0][dirCluster]; - if ( nextCluster != 0xFFFFFFFF ) { - FlushFileEntries( nextCluster & 0x7FFFFFFF, remainingFiles - 2, dirPath, parent ); + if ( nextCluster != ( LastDataCluster | DataClusterInUseMask ) ) { + FlushFileEntries( nextCluster & NextDataClusterMask, remainingFiles - 2, dirPath, parent ); } } @@ -984,7 +987,7 @@ bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { const u32 fatCluster = cluster - m_superBlock.data.alloc_offset; // if the cluster is unused according to FAT, just skip all this, we're not gonna find anything anyway - if ( ( m_fat.data[0][0][fatCluster] & 0x80000000 ) == 0 ) { + if ( ( m_fat.data[0][0][fatCluster] & DataClusterInUseMask ) == 0 ) { return false; } @@ -997,7 +1000,7 @@ bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { if ( file->IsOpened() ) { const u32 clusterOffset = ( page % 2 ) * PageSize + offset; const u32 fileSize = entry->entry.data.length; - const u32 fileOffsetStart = std::min( clusterNumber * ClusterSize + clusterOffset, fileSize );; + const u32 fileOffsetStart = std::min( clusterNumber * ClusterSize + clusterOffset, fileSize ); const u32 fileOffsetEnd = std::min( fileOffsetStart + dataLength, fileSize ); const u32 bytesToWrite = fileOffsetEnd - fileOffsetStart; @@ -1042,7 +1045,7 @@ s32 FolderMemoryCard::EraseBlock( u32 adr ) { return 1; } -u64 FolderMemoryCard::GetCRC() { +u64 FolderMemoryCard::GetCRC() const { // Since this is just used as integrity check for savestate loading, // give a timestamp of the last time the memory card was written to return m_timeLastWritten; @@ -1053,7 +1056,7 @@ void FolderMemoryCard::SetSlot( uint slot ) { m_slot = slot; } -u32 FolderMemoryCard::GetSizeInClusters() { +u32 FolderMemoryCard::GetSizeInClusters() const { const u32 clusters = m_superBlock.data.clusters_per_card; if ( clusters > 0 && clusters < 0xFFFFFFFFu ) { return clusters; @@ -1229,7 +1232,7 @@ bool FileAccessHelper::CleanMemcardFilename( char* name ) { } -bool MemoryCardFileMetadataReference::GetPath( wxFileName* fileName ) { +bool MemoryCardFileMetadataReference::GetPath( wxFileName* fileName ) const { bool parentCleaned = false; if ( parent ) { parentCleaned = parent->GetPath( fileName ); diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 554414380..a9e744f28 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -92,6 +92,25 @@ struct MemoryCardFileEntryDateTime { // Structure for directory and file relationships as stored on memory cards #pragma pack(push, 1) struct MemoryCardFileEntry { + enum MemoryCardFileModeFlags { + Mode_Read = 0x0001, + Mode_Write = 0x0002, + Mode_Execute = 0x0004, + Mode_CopyProtected = 0x0008, + Mode_File = 0x0010, + Mode_Directory = 0x0020, + Mode_Unknown0x0040 = 0x0040, + Mode_Unknown0x0080 = 0x0080, + Mode_Unknown0x0100 = 0x0100, + Mode_Unknown0x0200 = 0x0200, + Mode_Unknown0x0400 = 0x0400, // Maybe Mode_PS2_Save or something along those lines? + Mode_PocketStation = 0x0800, + Mode_PSX = 0x1000, + Mode_Unknown0x2000 = 0x2000, // Supposedly Mode_Hidden but files still show up in the PS2 browser with this set + Mode_Unknown0x4000 = 0x4000, + Mode_Used = 0x8000 + }; + union { struct MemoryCardFileEntryData { u32 mode; @@ -108,15 +127,15 @@ struct MemoryCardFileEntry { u8 raw[0x200]; } entry; - bool IsFile() { return !!( entry.data.mode & 0x0010 ); } - bool IsDir() { return !!( entry.data.mode & 0x0020 ); } - bool IsUsed() { return !!( entry.data.mode & 0x8000 ); } - bool IsValid() { return entry.data.mode != 0xFFFFFFFF; } + bool IsFile() const { return !!( entry.data.mode & Mode_File ); } + bool IsDir() const { return !!( entry.data.mode & Mode_Directory ); } + bool IsUsed() const { return !!( entry.data.mode & Mode_Used ); } + bool IsValid() const { return entry.data.mode != 0xFFFFFFFF; } // checks if we're either "." or ".." - bool IsDotDir() { return entry.data.name[0] == '.' && ( entry.data.name[1] == '\0' || ( entry.data.name[1] == '.' && entry.data.name[2] == '\0' ) ); } + bool IsDotDir() const { return entry.data.name[0] == '.' && ( entry.data.name[1] == '\0' || ( entry.data.name[1] == '.' && entry.data.name[2] == '\0' ) ); } - static const u32 DefaultDirMode = 0x8427; - static const u32 DefaultFileMode = 0x8497; + static const u32 DefaultDirMode = Mode_Read | Mode_Write | Mode_Execute | Mode_Directory | Mode_Unknown0x0400 | Mode_Used; + static const u32 DefaultFileMode = Mode_Read | Mode_Write | Mode_Execute | Mode_File | Mode_Unknown0x0080 | Mode_Unknown0x0400 | Mode_Used; }; #pragma pack(pop) @@ -143,7 +162,7 @@ struct MemoryCardFileMetadataReference { u32 consecutiveCluster; // returns true if filename was modified and metadata containing the actual filename should be written - bool GetPath( wxFileName* fileName ); + bool GetPath( wxFileName* fileName ) const; }; // -------------------------------------------------------------------------------------- @@ -194,6 +213,11 @@ public: static const int TotalBlocks = TotalClusters / 8; static const int TotalSizeRaw = TotalPages * PageSizeRaw; + static const u32 IndirectFatUnused = 0xFFFFFFFFu; + static const u32 LastDataCluster = 0x7FFFFFFFu; + static const u32 NextDataClusterMask = 0x7FFFFFFFu; + static const u32 DataClusterInUseMask = 0x80000000u; + static const int FramesAfterWriteUntilFlush = 60; protected: @@ -252,17 +276,17 @@ public: void Open( const wxString& fullPath, const AppConfig::McdOptions& mcdOptions, const bool enableFiltering, const wxString& filter ); void Close(); - s32 IsPresent(); - void GetSizeInfo( PS2E_McdSizeInfo& outways ); - bool IsPSX(); + s32 IsPresent() const; + void GetSizeInfo( PS2E_McdSizeInfo& outways ) const; + bool IsPSX() const; s32 Read( u8 *dest, u32 adr, int size ); s32 Save( const u8 *src, u32 adr, int size ); s32 EraseBlock( u32 adr ); - u64 GetCRC(); + u64 GetCRC() const; void SetSlot( uint slot ); - u32 GetSizeInClusters(); + u32 GetSizeInClusters() const; // WARNING: The intended use-case for this is resetting back to 8MB if a differently-sized superblock was loaded // setting to a different size is untested and will probably not work correctly @@ -279,7 +303,7 @@ protected: // initializes memory card data, as if it was fresh from the factory void InitializeInternalData(); - bool IsFormatted(); + bool IsFormatted() const; // returns the in-memory address of data the given memory card adr corresponds to // returns nullptr if adr corresponds to a folder or file entry @@ -326,22 +350,25 @@ protected: // returns the system cluster past the highest used one (will be the lowest free one under normal use) // this is used for creating the FAT, don't call otherwise unless you know exactly what you're doing - u32 GetFreeSystemCluster(); + u32 GetFreeSystemCluster() const; + + // returns the total amount of data clusters available on the memory card, both used and unused + u32 GetAmountDataClusters() const; // returns the lowest unused data cluster, relative to alloc_offset in the superblock // returns 0xFFFFFFFFu when the memory card is full - u32 GetFreeDataCluster(); + u32 GetFreeDataCluster() const; // returns the amount of unused data clusters - u32 GetAmountFreeDataClusters(); + u32 GetAmountFreeDataClusters() const; // returns the final cluster of the file or directory which is (partially) stored in the given cluster - u32 GetLastClusterOfData( const u32 cluster ); + u32 GetLastClusterOfData( const u32 cluster ) const; // creates and returns a new file entry in the given directory entry, ready to be filled // returns nullptr when the memory card is full - MemoryCardFileEntry* AppendFileEntryToDir( MemoryCardFileEntry* const dirEntry ); + MemoryCardFileEntry* AppendFileEntryToDir( const MemoryCardFileEntry* const dirEntry ); // adds a folder in the host file system to the memory card, including all files and subdirectories // - dirEntry: the entry of the directory in the parent directory, or the root "." entry @@ -358,7 +385,7 @@ protected: bool AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName, MemoryCardFileMetadataReference* parent = nullptr ); // calculates the amount of clusters a directory would use up if put into a memory card - u32 CalculateRequiredClustersOfDirectory( const wxString& dirPath ); + u32 CalculateRequiredClustersOfDirectory( const wxString& dirPath ) const; // adds a file to the quick-access dictionary, so it can be accessed more efficiently (ie, without searching through the entire file system) later From 8e92d25b75d2bb78c1c73bf6935eab5a66c67399 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 29 Jun 2015 04:05:31 +0200 Subject: [PATCH 39/44] FolderMemoryCard: Add support for deleting of files/folders. We're not actually deleting files though, we just rename them to prepend _pcsx2_deleted_, in case something breaks or whatever, so the user can in an emergency just restore the save by removing that part of the filename. --- pcsx2/gui/MemoryCardFolder.cpp | 101 ++++++++++++++++++++++++++++++++- pcsx2/gui/MemoryCardFolder.h | 23 ++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 101259e03..88bc7cace 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 a9e744f28..906d376e0 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(); From 3a553605720ce4555bcff25134b14f8048ec5232 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 4 Jul 2015 00:39:42 +0200 Subject: [PATCH 40/44] FolderMemoryCard: Fix a bug where the cache wouldn't be populated properly on first write to any given page. I'm kinda surprised this didn't horribly break things, honestly. I guess it's because memory card data is always written in blocks, but still. --- pcsx2/gui/MemoryCardFolder.cpp | 22 +++++++++++++--------- pcsx2/gui/MemoryCardFolder.h | 5 +++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 88bc7cace..3b64cc0ac 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -729,14 +729,7 @@ s32 FolderMemoryCard::Read( u8 *dest, u32 adr, int size ) { if ( it != m_cache.end() ) { memcpy( dest, &it->second.raw[offset], dataLength ); } else { - u8* src = GetSystemBlockPointer( adr ); - if ( src != nullptr ) { - memcpy( dest, src, dataLength ); - } else { - if ( !ReadFromFile( dest, adr, dataLength ) ) { - memset( dest, 0xFF, dataLength ); - } - } + ReadDataWithoutCache( dest, adr, dataLength ); } } @@ -765,6 +758,17 @@ s32 FolderMemoryCard::Read( u8 *dest, u32 adr, int size ) { return 1; } +void FolderMemoryCard::ReadDataWithoutCache( u8* const dest, const u32 adr, const u32 dataLength ) { + u8* src = GetSystemBlockPointer( adr ); + if ( src != nullptr ) { + memcpy( dest, src, dataLength ); + } else { + if ( !ReadFromFile( dest, adr, dataLength ) ) { + memset( dest, 0xFF, dataLength ); + } + } +} + s32 FolderMemoryCard::Save( const u8 *src, u32 adr, int size ) { const u32 block = adr / BlockSizeRaw; const u32 cluster = adr / ClusterSizeRaw; @@ -790,7 +794,7 @@ s32 FolderMemoryCard::Save( const u8 *src, u32 adr, int size ) { if ( it == m_cache.end() ) { cachePage = &m_cache[page]; const u32 adrLoad = page * PageSizeRaw; - Read( &cachePage->raw[0], adrLoad, PageSize ); + ReadDataWithoutCache( &cachePage->raw[0], adrLoad, PageSize ); } else { cachePage = &it->second; } diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 906d376e0..cfa3b7325 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -402,6 +402,11 @@ protected: // creates a reference to a directory entry, so it can be passed as parent to other files/directories MemoryCardFileMetadataReference* AddDirEntryToMetadataQuickAccess( MemoryCardFileEntry* const entry, MemoryCardFileMetadataReference* const parent ); + + // read data from the memory card, ignoring the cache + // do NOT attempt to read ECC with this method, it will not work + void ReadDataWithoutCache( u8* const dest, const u32 adr, const u32 dataLength ); + bool ReadFromFile( u8 *dest, u32 adr, u32 dataLength ); bool WriteToFile( const u8* src, u32 adr, u32 dataLength ); From 03a6be28c056c7b3e6fb977bde07ed45e4b92f06 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 4 Jul 2015 01:42:17 +0200 Subject: [PATCH 41/44] FolderMemoryCard: Write the SuperBlock as part of Flush() instead of when the card is Close()d. This mainly means that the superblock is now no longer written every single time the memory card is closed, but only when it's changed (which should be exactly once, when the memory card is formatted). It also means that you can format a memory card and then have the emulator crash later without having to reformat the card next time. --- pcsx2/gui/MemoryCardFolder.cpp | 53 ++++++++++++++++++++++------------ pcsx2/gui/MemoryCardFolder.h | 9 ++++-- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 3b64cc0ac..aad098d9c 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -99,12 +99,6 @@ void FolderMemoryCard::Close() { Flush(); - wxFileName superBlockFileName( m_folderName.GetPath(), L"_pcsx2_superblock" ); - wxFFile superBlockFile( superBlockFileName.GetFullPath().c_str(), L"wb" ); - if ( superBlockFile.IsOpened() ) { - superBlockFile.Write( &m_superBlock.raw, sizeof( m_superBlock.raw ) ); - } - m_cache.clear(); m_fileMetadataQuickAccess.clear(); m_lastAccessedFile.Close(); @@ -828,7 +822,7 @@ void FolderMemoryCard::Flush() { } // first write the superblock if necessary - FlushBlock( 0 ); + FlushSuperBlock(); if ( !IsFormatted() ) { return; } // check if we were interrupted in the middle of a save operation, if yes abort @@ -877,24 +871,40 @@ void FolderMemoryCard::Flush() { Console.WriteLn( L"(FolderMcd) Done! Took %u ms.", timeFlushEnd - timeFlushStart ); } -void FolderMemoryCard::FlushPage( const u32 page ) { +bool FolderMemoryCard::FlushPage( const u32 page ) { auto it = m_cache.find( page ); if ( it != m_cache.end() ) { WriteWithoutCache( &it->second.raw[0], page * PageSizeRaw, PageSize ); m_cache.erase( it ); + return true; } + return false; } -void FolderMemoryCard::FlushCluster( const u32 cluster ) { +bool FolderMemoryCard::FlushCluster( const u32 cluster ) { const u32 page = cluster * 2; - FlushPage( page ); - FlushPage( page + 1 ); + bool flushed = false; + if ( FlushPage( page ) ) { flushed = true; } + if ( FlushPage( page + 1 ) ) { flushed = true; } + return flushed; } -void FolderMemoryCard::FlushBlock( const u32 block ) { +bool FolderMemoryCard::FlushBlock( const u32 block ) { const u32 page = block * 16; + bool flushed = false; for ( int i = 0; i < 16; ++i ) { - FlushPage( page + i ); + if ( FlushPage( page + i ) ) { flushed = true; } + } + return flushed; +} + +void FolderMemoryCard::FlushSuperBlock() { + if ( FlushBlock( 0 ) ) { + wxFileName superBlockFileName( m_folderName.GetPath(), L"_pcsx2_superblock" ); + wxFFile superBlockFile( superBlockFileName.GetFullPath().c_str(), L"wb" ); + if ( superBlockFile.IsOpened() ) { + superBlockFile.Write( &m_superBlock.raw, sizeof( m_superBlock.raw ) ); + } } } @@ -1169,15 +1179,22 @@ u32 FolderMemoryCard::GetSizeInClusters() const { } void FolderMemoryCard::SetSizeInClusters( u32 clusters ) { - m_superBlock.data.clusters_per_card = clusters; + superBlockUnion newSuperBlock; + memcpy( &newSuperBlock.raw[0], &m_superBlock.raw[0], sizeof( newSuperBlock.raw ) ); + + newSuperBlock.data.clusters_per_card = clusters; const u32 alloc_offset = clusters / 0x100 + 9; - m_superBlock.data.alloc_offset = alloc_offset; - m_superBlock.data.alloc_end = clusters - 0x10 - alloc_offset; + newSuperBlock.data.alloc_offset = alloc_offset; + newSuperBlock.data.alloc_end = clusters - 0x10 - alloc_offset; const u32 blocks = clusters / 8; - m_superBlock.data.backup_block1 = blocks - 1; - m_superBlock.data.backup_block2 = blocks - 2; + newSuperBlock.data.backup_block1 = blocks - 1; + newSuperBlock.data.backup_block2 = blocks - 2; + + for ( size_t i = 0; i < sizeof( newSuperBlock.raw ) / PageSize; ++i ) { + Save( &newSuperBlock.raw[i * PageSize], i * PageSizeRaw, PageSize ); + } } void FolderMemoryCard::SetSizeInMB( u32 megaBytes ) { diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index cfa3b7325..ae1109e6b 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -416,13 +416,16 @@ protected: void Flush(); // flush a single page of the cache to the internal data and/or host file system - void FlushPage( const u32 page ); + bool FlushPage( const u32 page ); // flush a memory card cluster of the cache to the internal data and/or host file system - void FlushCluster( const u32 cluster ); + bool FlushCluster( const u32 cluster ); // flush a whole memory card block of the cache to the internal data and/or host file system - void FlushBlock( const u32 block ); + bool FlushBlock( const u32 block ); + + // flush the superblock to the internal data and/or host file system + void FlushSuperBlock(); // flush all directory and file entries to the internal data void FlushFileEntries(); From 6bd578ccbe43d1a0effb8f428ce828820014d3b8 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 3 Jul 2015 23:01:06 +0200 Subject: [PATCH 42/44] FolderMemoryCard: Reduce unnecessary file I/O by only flushing files that have actually changed since the last known memory card state on the host file system. This means that we are now no longer touching files that haven't technically been written to. Some games use timestamp information to automatically highlight the save that was last written to, so this should fix a small but annoying bug where it would highlight the wrong one. Do note that while there is a much simpler check that looks like this: // Remove (== don't flush) all memory card pages that haven't actually changed. for ( auto oldIt = m_oldDataCache.begin(); oldIt != m_oldDataCache.end(); ++oldIt ) { auto newIt = m_cache.find( oldIt->first ); assert( newIt != m_cache.end() ); // if this isn't true something broke somewhere, the two maps should always contain the same pages if ( memcmp( &oldIt->second.raw[0], &newIt->second.raw[0], PageSize ) == 0 ) { m_cache.erase( newIt ); } } m_oldDataCache.clear(); It can fail in edge cases that don't actually seem too unlikely. Imagine a save being deleted, and then a new save from the same game but in a different slot being created quickly afterwards. It seems quite possible that the new save's file data then occupies the exact same pages as the old save's, and since it's from the same game it might be close enough to where a page sized section (juse 0x200 bytes!) matches the data from the old save that previously resided in that location -- which would cause this code to throw away and not flush this data! It's a shame too, since this variant would be a few ms faster as well, but I feel it's better to be safe than sorry here. --- pcsx2/gui/MemoryCardFolder.cpp | 47 ++++++++++++++++++++++++++++++---- pcsx2/gui/MemoryCardFolder.h | 21 +++++++++++++-- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index aad098d9c..de24a365a 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -38,6 +38,7 @@ void FolderMemoryCard::InitializeInternalData() { memset( &m_backupBlock1, 0xFF, sizeof( m_backupBlock1 ) ); memset( &m_backupBlock2, 0xFF, sizeof( m_backupBlock2 ) ); m_cache.clear(); + m_oldDataCache.clear(); m_fileMetadataQuickAccess.clear(); m_timeLastWritten = 0; m_isEnabled = false; @@ -100,6 +101,7 @@ void FolderMemoryCard::Close() { Flush(); m_cache.clear(); + m_oldDataCache.clear(); m_fileMetadataQuickAccess.clear(); m_lastAccessedFile.Close(); } @@ -789,6 +791,7 @@ s32 FolderMemoryCard::Save( const u8 *src, u32 adr, int size ) { cachePage = &m_cache[page]; const u32 adrLoad = page * PageSizeRaw; ReadDataWithoutCache( &cachePage->raw[0], adrLoad, PageSize ); + memcpy( &m_oldDataCache[page].raw[0], &cachePage->raw[0], PageSize ); } else { cachePage = &it->second; } @@ -858,7 +861,7 @@ void FolderMemoryCard::Flush() { 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 ); + FlushDeletedFilesAndRemoveUnchangedDataFromCache( oldFileEntryTree ); // and finally, flush everything that hasn't been flushed yet for ( uint i = 0; i < pageCount; ++i ) { @@ -866,6 +869,7 @@ void FolderMemoryCard::Flush() { } m_lastAccessedFile.Close(); + m_oldDataCache.clear(); const u64 timeFlushEnd = wxGetLocalTimeMillis().GetValue(); Console.WriteLn( L"(FolderMcd) Done! Took %u ms.", timeFlushEnd - timeFlushStart ); @@ -969,14 +973,14 @@ void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remaini } } -void FolderMemoryCard::FlushDeletedFiles( const std::vector& oldFileEntries ) { +void FolderMemoryCard::FlushDeletedFilesAndRemoveUnchangedDataFromCache( 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 ); + FlushDeletedFilesAndRemoveUnchangedDataFromCache( oldFileEntries, newRootDirCluster, newFileCount, path ); } -void FolderMemoryCard::FlushDeletedFiles( const std::vector& oldFileEntries, const u32 newCluster, const u32 newFileCount, const wxString& dirPath ) { +void FolderMemoryCard::FlushDeletedFilesAndRemoveUnchangedDataFromCache( 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; @@ -1003,12 +1007,45 @@ void FolderMemoryCard::FlushDeletedFiles( const std::vectorsubdir, newEntry->entry.data.cluster, newEntry->entry.data.length, subDirPath ); + FlushDeletedFilesAndRemoveUnchangedDataFromCache( it->subdir, newEntry->entry.data.cluster, newEntry->entry.data.length, subDirPath ); + } else if ( entry->IsFile() ) { + // still exists and is a file, see if we can remove unchanged data from m_cache + RemoveUnchangedDataFromCache( entry, newEntry ); } } } } +void FolderMemoryCard::RemoveUnchangedDataFromCache( const MemoryCardFileEntry* const oldEntry, const MemoryCardFileEntry* const newEntry ) { + // Disclaimer: Technically, to actually prove that file data has not changed and still belongs to the same file, we'd need to keep a copy + // of the old FAT cluster chain and compare that as well, and only acknowledge the file as unchanged if none of those have changed. However, + // the chain of events that leads to a file having the exact same file contents as a deleted old file while also being placed in the same + // data clusters as the deleted file AND matching this condition here, in a quick enough succession that no flush has occurred yet since the + // deletion of that old file is incredibly unlikely, so I'm not sure if it's actually worth coding for. + if ( oldEntry->entry.data.timeModified != newEntry->entry.data.timeModified || oldEntry->entry.data.timeCreated != newEntry->entry.data.timeCreated + || oldEntry->entry.data.length != newEntry->entry.data.length || oldEntry->entry.data.cluster != newEntry->entry.data.cluster ) { + return; + } + + u32 cluster = newEntry->entry.data.cluster & NextDataClusterMask; + const u32 alloc_offset = m_superBlock.data.alloc_offset; + while ( cluster != LastDataCluster ) { + for ( int i = 0; i < 2; ++i ) { + const u32 page = ( cluster + alloc_offset ) * 2 + i; + auto newIt = m_cache.find( page ); + if ( newIt == m_cache.end() ) { continue; } + auto oldIt = m_oldDataCache.find( page ); + if ( oldIt == m_oldDataCache.end() ) { continue; } + + if ( memcmp( &oldIt->second.raw[0], &newIt->second.raw[0], PageSize ) == 0 ) { + m_cache.erase( newIt ); + } + } + + cluster = m_fat.data[0][0][cluster] & NextDataClusterMask; + } +} + s32 FolderMemoryCard::WriteWithoutCache( const u8 *src, u32 adr, int size ) { const u32 block = adr / BlockSizeRaw; const u32 cluster = adr / ClusterSizeRaw; diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index ae1109e6b..92002187a 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -84,6 +84,14 @@ struct MemoryCardFileEntryDateTime { return t; } + + bool operator==( const MemoryCardFileEntryDateTime& other ) const { + return unused == other.unused && second == other.second && minute == other.minute && hour == other.hour + && day == other.day && month == other.month && year == other.year; + } + bool operator!=( const MemoryCardFileEntryDateTime& other ) const { + return !( *this == other ); + } }; #pragma pack(pop) @@ -254,6 +262,10 @@ protected: // holds a copy of modified pages of the memory card before they're flushed to the file system std::map m_cache; + // contains the state of how the data looked before the first write to it + // used to reduce the amount of disk I/O by not re-writing unchanged data that just happened to be + // touched in memory due to how actual physical memory cards have to erase and rewrite in blocks + std::map m_oldDataCache; // if > 0, the amount of frames until data is flushed to the file system // reset to FramesAfterWriteUntilFlush on each write int m_framesUntilFlush; @@ -434,13 +446,18 @@ protected: 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 ); + // also calls RemoveUnchangedDataFromCache() since both operate on comparing with the old file entires + void FlushDeletedFilesAndRemoveUnchangedDataFromCache( 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 ); + void FlushDeletedFilesAndRemoveUnchangedDataFromCache( const std::vector& oldFileEntries, const u32 newCluster, const u32 newFileCount, const wxString& dirPath ); + + // try and remove unchanged data from m_cache + // oldEntry and newEntry should be equivalent entries found by FindEquivalent() + void RemoveUnchangedDataFromCache( const MemoryCardFileEntry* const oldEntry, const MemoryCardFileEntry* const newEntry ); // write data as Save() normally would, but ignore the cache; used for flushing s32 WriteWithoutCache( const u8 *src, u32 adr, int size ); From 39e1de4d13e8b68388d2e6744d50932a04b2b57e Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Tue, 14 Jul 2015 17:40:22 +0200 Subject: [PATCH 43/44] MemoryCard: Add options to convert FolderMemoryCards to 16MB, 32MB, and 64MB FileMemoryCards. --- pcsx2/gui/Dialogs/ConfigurationDialog.h | 2 +- pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp | 58 ++++++++++++++----- pcsx2/gui/MemoryCardFolder.cpp | 19 ++++-- pcsx2/gui/MemoryCardFolder.h | 8 ++- 4 files changed, 61 insertions(+), 26 deletions(-) diff --git a/pcsx2/gui/Dialogs/ConfigurationDialog.h b/pcsx2/gui/Dialogs/ConfigurationDialog.h index faaf3cc30..25de2698a 100644 --- a/pcsx2/gui/Dialogs/ConfigurationDialog.h +++ b/pcsx2/gui/Dialogs/ConfigurationDialog.h @@ -256,7 +256,7 @@ namespace Dialogs protected: void CreateControls( const MemoryCardType sourceType ); void OnOk_Click( wxCommandEvent& evt ); - bool ConvertToFile( const wxFileName& sourcePath, const wxFileName& targetPath ); + bool ConvertToFile( const wxFileName& sourcePath, const wxFileName& targetPath, const u32 sizeInMB ); bool ConvertToFolder( const wxFileName& sourcePath, const wxFileName& targetPath ); }; } diff --git a/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp b/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp index 0185cdc5b..e36641e23 100644 --- a/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp +++ b/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp @@ -21,6 +21,15 @@ #include "MemoryCardFolder.h" #include +enum MemoryCardConversionType { + MemoryCardConversion_File_8MB, + MemoryCardConversion_File_16MB, + MemoryCardConversion_File_32MB, + MemoryCardConversion_File_64MB, + MemoryCardConversion_Folder, + MemoryCardConversion_MaxCount +}; + Dialogs::ConvertMemoryCardDialog::ConvertMemoryCardDialog( wxWindow* parent, const wxDirName& mcdPath, const AppConfig::McdOptions& mcdSourceConfig ) : wxDialogWithHelpers( parent, _( "Convert a memory card to a different format" ) ) , m_mcdPath( mcdPath ) @@ -52,7 +61,10 @@ Dialogs::ConvertMemoryCardDialog::ConvertMemoryCardDialog( wxWindow* parent, con s_padding += m_radio_CardType | pxSizerFlags::StdExpand(); - s_padding += Heading( pxE( L"WARNING: Converting a memory card may take several minutes! Please do not close the emulator during the conversion process, even if the emulator is no longer responding to input." ) ); + if ( mcdSourceConfig.Type != MemoryCardType::MemoryCard_File ) { + s_padding += Heading( pxE( L"Please note that the resulting file may not actually contain all saves, depending on how many are in the source memory card." ) ); + } + s_padding += Heading( pxE( L"WARNING: Converting a memory card may take a while! Please do not close the emulator during the conversion process, even if the emulator is no longer responding to input." ) ); s_padding += 12; s_padding += s_buttons | pxSizerFlags::StdCenter(); @@ -69,13 +81,19 @@ Dialogs::ConvertMemoryCardDialog::ConvertMemoryCardDialog( wxWindow* parent, con void Dialogs::ConvertMemoryCardDialog::CreateControls( const MemoryCardType sourceType ) { m_text_filenameInput = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER ); - RadioPanelItem toFile = RadioPanelItem( _( "File" ), pxE( L"Convert this memory card to a regular 8 MB .ps2 file. Please note that the resulting file may not actually contain all saves, depending on how many are in the source memory card." ) ) - .SetInt( MemoryCardType::MemoryCard_File ); + RadioPanelItem toFile8MB = RadioPanelItem( _( "8MB File" ), pxE( L"Convert this memory card to a standard 8 MB Memory Card .ps2 file." ) ) + .SetInt( MemoryCardConversionType::MemoryCardConversion_File_8MB ); + RadioPanelItem toFile16MB = RadioPanelItem( _( "16MB File" ), pxE( L"Convert this memory card to a 16 MB Memory Card .ps2 file." ) ) + .SetInt( MemoryCardConversionType::MemoryCardConversion_File_16MB ); + RadioPanelItem toFile32MB = RadioPanelItem( _( "32MB File" ), pxE( L"Convert this memory card to a 32 MB Memory Card .ps2 file." ) ) + .SetInt( MemoryCardConversionType::MemoryCardConversion_File_32MB ); + RadioPanelItem toFile64MB = RadioPanelItem( _( "64MB File" ), pxE( L"Convert this memory card to a 64 MB Memory Card .ps2 file." ) ) + .SetInt( MemoryCardConversionType::MemoryCardConversion_File_64MB ); RadioPanelItem toFolder = RadioPanelItem( _( "Folder" ), _( "Convert this memory card to a folder of individual saves." ) ) - .SetInt( MemoryCardType::MemoryCard_Folder ); + .SetInt( MemoryCardConversionType::MemoryCardConversion_Folder ); const RadioPanelItem tblForFile[] = { toFolder }; - const RadioPanelItem tblForFolder[] = { toFile }; + const RadioPanelItem tblForFolder[] = { toFile8MB, toFile16MB, toFile32MB, toFile64MB }; switch ( sourceType ) { case MemoryCardType::MemoryCard_File: @@ -110,13 +128,22 @@ void Dialogs::ConvertMemoryCardDialog::OnOk_Click( wxCommandEvent& evt ) { wxFileName sourcePath = ( m_mcdPath + m_mcdSourceFilename ); wxFileName targetPath = ( m_mcdPath + composedName ); if ( m_radio_CardType ) { - MemoryCardType targetType = (MemoryCardType)m_radio_CardType->SelectedItem().SomeInt; + MemoryCardConversionType targetType = (MemoryCardConversionType)m_radio_CardType->SelectedItem().SomeInt; switch ( targetType ) { - case MemoryCardType::MemoryCard_File: - success = ConvertToFile( sourcePath, targetPath ); + case MemoryCardConversionType::MemoryCardConversion_File_8MB: + success = ConvertToFile( sourcePath, targetPath, 8 ); break; - case MemoryCardType::MemoryCard_Folder: + case MemoryCardConversionType::MemoryCardConversion_File_16MB: + success = ConvertToFile( sourcePath, targetPath, 16 ); + break; + case MemoryCardConversionType::MemoryCardConversion_File_32MB: + success = ConvertToFile( sourcePath, targetPath, 32 ); + break; + case MemoryCardConversionType::MemoryCardConversion_File_64MB: + success = ConvertToFile( sourcePath, targetPath, 64 ); + break; + case MemoryCardConversionType::MemoryCardConversion_Folder: success = ConvertToFolder( sourcePath, targetPath ); break; default: @@ -133,7 +160,7 @@ void Dialogs::ConvertMemoryCardDialog::OnOk_Click( wxCommandEvent& evt ) { EndModal( wxID_OK ); } -bool Dialogs::ConvertMemoryCardDialog::ConvertToFile( const wxFileName& sourcePath, const wxFileName& targetPath ) { +bool Dialogs::ConvertMemoryCardDialog::ConvertToFile( const wxFileName& sourcePath, const wxFileName& targetPath, const u32 sizeInMB ) { // Conversion method: Open FolderMcd as usual, then read the raw data from it and write it to a file stream wxFFile targetFile( targetPath.GetFullPath(), L"wb" ); @@ -145,25 +172,24 @@ bool Dialogs::ConvertMemoryCardDialog::ConvertToFile( const wxFileName& sourcePa AppConfig::McdOptions config; config.Enabled = true; config.Type = MemoryCardType::MemoryCard_Folder; - sourceFolderMemoryCard.Open( sourcePath.GetFullPath(), config, false, L"" ); + sourceFolderMemoryCard.Open( sourcePath.GetFullPath(), config, ( sizeInMB * 1024 * 1024 ) / FolderMemoryCard::ClusterSize, false, L"" ); u8 buffer[FolderMemoryCard::PageSizeRaw]; u32 adr = 0; - while ( adr < FolderMemoryCard::TotalSizeRaw ) { + while ( adr < sourceFolderMemoryCard.GetSizeInClusters() * FolderMemoryCard::ClusterSizeRaw ) { sourceFolderMemoryCard.Read( buffer, adr, FolderMemoryCard::PageSizeRaw ); targetFile.Write( buffer, FolderMemoryCard::PageSizeRaw ); adr += FolderMemoryCard::PageSizeRaw; } targetFile.Close(); - sourceFolderMemoryCard.Close(); + sourceFolderMemoryCard.Close( false ); return true; } bool Dialogs::ConvertMemoryCardDialog::ConvertToFolder( const wxFileName& sourcePath, const wxFileName& targetPath ) { // Conversion method: Read all pages of the FileMcd into a FolderMcd, then just write that out with the regular methods - // TODO: Test if >8MB files don't super fuck up something wxFFile sourceFile( sourcePath.GetFullPath(), L"rb" ); if ( !sourceFile.IsOpened() ) { @@ -175,7 +201,7 @@ bool Dialogs::ConvertMemoryCardDialog::ConvertToFolder( const wxFileName& source AppConfig::McdOptions config; config.Enabled = true; config.Type = MemoryCardType::MemoryCard_Folder; - targetFolderMemoryCard.Open( targetPath.GetFullPath(), config, false, L"" ); + targetFolderMemoryCard.Open( targetPath.GetFullPath(), config, 0, false, L"" ); u32 adr = 0; while ( !sourceFile.Eof() ) { @@ -191,7 +217,7 @@ bool Dialogs::ConvertMemoryCardDialog::ConvertToFolder( const wxFileName& source if ( adr != FolderMemoryCard::TotalSizeRaw ) { // reset memory card metrics in superblock to the default 8MB, since the converted card was different - targetFolderMemoryCard.Open( targetPath.GetFullPath(), config, true, L"" ); + targetFolderMemoryCard.Open( targetPath.GetFullPath(), config, 0, true, L"" ); targetFolderMemoryCard.SetSizeInMB( 8 ); targetFolderMemoryCard.Close(); } diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index de24a365a..0266b70fa 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -52,10 +52,10 @@ bool FolderMemoryCard::IsFormatted() const { } void FolderMemoryCard::Open( const bool enableFiltering, const wxString& filter ) { - Open( g_Conf->FullpathToMcd( m_slot ), g_Conf->Mcd[m_slot], enableFiltering, filter ); + Open( g_Conf->FullpathToMcd( m_slot ), g_Conf->Mcd[m_slot], 0, enableFiltering, filter ); } -void FolderMemoryCard::Open( const wxString& fullPath, const AppConfig::McdOptions& mcdOptions, const bool enableFiltering, const wxString& filter ) { +void FolderMemoryCard::Open( const wxString& fullPath, const AppConfig::McdOptions& mcdOptions, const u32 sizeInClusters, const bool enableFiltering, const wxString& filter ) { InitializeInternalData(); wxFileName configuredFileName( fullPath ); @@ -89,16 +89,18 @@ void FolderMemoryCard::Open( const wxString& fullPath, const AppConfig::McdOptio if ( disabled ) return; m_isEnabled = true; - LoadMemoryCardData( enableFiltering, filter ); + LoadMemoryCardData( sizeInClusters, enableFiltering, filter ); SetTimeLastWrittenToNow(); m_framesUntilFlush = 0; } -void FolderMemoryCard::Close() { +void FolderMemoryCard::Close( bool flush ) { if ( !m_isEnabled ) { return; } - Flush(); + if ( flush ) { + Flush(); + } m_cache.clear(); m_oldDataCache.clear(); @@ -106,7 +108,7 @@ void FolderMemoryCard::Close() { m_lastAccessedFile.Close(); } -void FolderMemoryCard::LoadMemoryCardData( const bool enableFiltering, const wxString& filter ) { +void FolderMemoryCard::LoadMemoryCardData( const u32 sizeInClusters, const bool enableFiltering, const wxString& filter ) { bool formatted = false; // read superblock if it exists @@ -118,6 +120,11 @@ void FolderMemoryCard::LoadMemoryCardData( const bool enableFiltering, const wxS } } + if ( sizeInClusters > 0 && sizeInClusters != GetSizeInClusters() ) { + SetSizeInClusters( sizeInClusters ); + FlushBlock( 0 ); + } + // if superblock was valid, load folders and files if ( formatted ) { if ( enableFiltering ) { diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 92002187a..028a0bcd3 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -293,8 +293,9 @@ public: // Initialize & Load Memory Card with values configured in the Memory Card Manager void Open( const bool enableFiltering, const wxString& filter ); // Initialize & Load Memory Card with provided custom values - void Open( const wxString& fullPath, const AppConfig::McdOptions& mcdOptions, const bool enableFiltering, const wxString& filter ); - void Close(); + void Open( const wxString& fullPath, const AppConfig::McdOptions& mcdOptions, const u32 sizeInClusters, const bool enableFiltering, const wxString& filter ); + // Close the memory card and flush changes to the file system. Set flush to false to not store changes. + void Close( bool flush = true ); s32 IsPresent() const; void GetSizeInfo( PS2E_McdSizeInfo& outways ) const; @@ -357,9 +358,10 @@ protected: // loads files and folders from the host file system if a superblock exists in the root directory + // - sizeInClusters: total memory card size in clusters, 0 for default // - enableFiltering: if set to true, only folders whose name contain the filter string are loaded // - filter: can include multiple filters by separating them with "/" - void LoadMemoryCardData( const bool enableFiltering, const wxString& filter ); + void LoadMemoryCardData( const u32 sizeInClusters, const bool enableFiltering, const wxString& filter ); // creates the FAT and indirect FAT void CreateFat(); From d331d59a9f5aaa5e27435656f799cff162ba088c Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 20 Jul 2015 23:53:53 +0200 Subject: [PATCH 44/44] MemoryCard: When converting a file to a folder, simulate the conversion process once before writing the data. This prevents half-converted/corrupted cards by ensuring we crash before any actual writes occur. --- pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp | 26 ++++-- pcsx2/gui/MemoryCardFolder.cpp | 91 ++++++++++--------- pcsx2/gui/MemoryCardFolder.h | 5 +- 3 files changed, 71 insertions(+), 51 deletions(-) diff --git a/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp b/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp index e36641e23..acf13bb82 100644 --- a/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp +++ b/pcsx2/gui/Dialogs/ConvertMemoryCardDialog.cpp @@ -201,19 +201,29 @@ bool Dialogs::ConvertMemoryCardDialog::ConvertToFolder( const wxFileName& source AppConfig::McdOptions config; config.Enabled = true; config.Type = MemoryCardType::MemoryCard_Folder; - targetFolderMemoryCard.Open( targetPath.GetFullPath(), config, 0, false, L"" ); - u32 adr = 0; - while ( !sourceFile.Eof() ) { - int size = sourceFile.Read( buffer, FolderMemoryCard::PageSizeRaw ); - if ( size > 0 ) { - targetFolderMemoryCard.Save( buffer, adr, size ); - adr += size; + + for ( int i = 0; i < 2; ++i ) { + // Before writing the data, we first simulate the entire process without actual writes to the file system. + // This ensures that if we crash/fail due to a corrupted memory card file system or similar, we do so during + // the simulation run, and don't actually write out any partial data to the host file system. + bool simulateWrites = i == 0; + targetFolderMemoryCard.Open( targetPath.GetFullPath(), config, 0, false, L"", simulateWrites ); + + adr = 0; + sourceFile.Seek( 0 ); + while ( !sourceFile.Eof() ) { + int size = sourceFile.Read( buffer, FolderMemoryCard::PageSizeRaw ); + if ( size > 0 ) { + targetFolderMemoryCard.Save( buffer, adr, size ); + adr += size; + } } + + targetFolderMemoryCard.Close(); } sourceFile.Close(); - targetFolderMemoryCard.Close(); if ( adr != FolderMemoryCard::TotalSizeRaw ) { // reset memory card metrics in superblock to the default 8MB, since the converted card was different diff --git a/pcsx2/gui/MemoryCardFolder.cpp b/pcsx2/gui/MemoryCardFolder.cpp index 0266b70fa..5b2c245ba 100644 --- a/pcsx2/gui/MemoryCardFolder.cpp +++ b/pcsx2/gui/MemoryCardFolder.cpp @@ -44,6 +44,7 @@ void FolderMemoryCard::InitializeInternalData() { m_isEnabled = false; m_framesUntilFlush = 0; m_lastAccessedFile.Close(); + m_performFileWrites = true; } bool FolderMemoryCard::IsFormatted() const { @@ -52,11 +53,12 @@ bool FolderMemoryCard::IsFormatted() const { } void FolderMemoryCard::Open( const bool enableFiltering, const wxString& filter ) { - Open( g_Conf->FullpathToMcd( m_slot ), g_Conf->Mcd[m_slot], 0, enableFiltering, filter ); + Open( g_Conf->FullpathToMcd( m_slot ), g_Conf->Mcd[m_slot], 0, enableFiltering, filter, false ); } -void FolderMemoryCard::Open( const wxString& fullPath, const AppConfig::McdOptions& mcdOptions, const u32 sizeInClusters, const bool enableFiltering, const wxString& filter ) { +void FolderMemoryCard::Open( const wxString& fullPath, const AppConfig::McdOptions& mcdOptions, const u32 sizeInClusters, const bool enableFiltering, const wxString& filter, bool simulateFileWrites ) { InitializeInternalData(); + m_performFileWrites = !simulateFileWrites; wxFileName configuredFileName( fullPath ); m_folderName = wxFileName( configuredFileName.GetFullPath() + L"/" ); @@ -74,7 +76,7 @@ void FolderMemoryCard::Open( const wxString& fullPath, const AppConfig::McdOptio } // if nothing exists at a valid location, create a directory for the memory card - if ( !disabled && !m_folderName.DirExists() ) { + if ( !disabled && m_performFileWrites && !m_folderName.DirExists() ) { if ( !m_folderName.Mkdir() ) { str = L"[couldn't create folder]"; disabled = true; @@ -910,7 +912,7 @@ bool FolderMemoryCard::FlushBlock( const u32 block ) { } void FolderMemoryCard::FlushSuperBlock() { - if ( FlushBlock( 0 ) ) { + if ( FlushBlock( 0 ) && m_performFileWrites ) { wxFileName superBlockFileName( m_folderName.GetPath(), L"_pcsx2_superblock" ); wxFFile superBlockFile( superBlockFileName.GetFullPath().c_str(), L"wb" ); if ( superBlockFile.IsOpened() ) { @@ -946,21 +948,23 @@ void FolderMemoryCard::FlushFileEntries( const u32 dirCluster, const u32 remaini const wxString subDirName = wxString::FromAscii( (const char*)cleanName ); const wxString subDirPath = dirPath + L"/" + subDirName; - // if this directory has nonstandard metadata, write that to the file system - wxFileName metaFileName( m_folderName.GetFullPath() + subDirPath + L"/_pcsx2_meta_directory" ); - if ( filenameCleaned || entry->entry.data.mode != MemoryCardFileEntry::DefaultDirMode || entry->entry.data.attr != 0 ) { - if ( !metaFileName.DirExists() ) { - metaFileName.Mkdir(); - } - wxFFile metaFile( metaFileName.GetFullPath(), L"wb" ); - if ( metaFile.IsOpened() ) { - metaFile.Write( entry->entry.raw, sizeof( entry->entry.raw ) ); - metaFile.Close(); - } - } else { - // if metadata is standard make sure to remove a possibly existing metadata file - if ( metaFileName.FileExists() ) { - wxRemoveFile( metaFileName.GetFullPath() ); + if ( m_performFileWrites ) { + // if this directory has nonstandard metadata, write that to the file system + wxFileName metaFileName( m_folderName.GetFullPath() + subDirPath + L"/_pcsx2_meta_directory" ); + if ( filenameCleaned || entry->entry.data.mode != MemoryCardFileEntry::DefaultDirMode || entry->entry.data.attr != 0 ) { + if ( !metaFileName.DirExists() ) { + metaFileName.Mkdir(); + } + wxFFile metaFile( metaFileName.GetFullPath(), L"wb" ); + if ( metaFile.IsOpened() ) { + metaFile.Write( entry->entry.raw, sizeof( entry->entry.raw ) ); + metaFile.Close(); + } + } else { + // if metadata is standard make sure to remove a possibly existing metadata file + if ( metaFileName.FileExists() ) { + wxRemoveFile( metaFileName.GetFullPath() ); + } } } @@ -1105,33 +1109,36 @@ bool FolderMemoryCard::WriteToFile( const u8* src, u32 adr, u32 dataLength ) { if ( it != m_fileMetadataQuickAccess.end() ) { const MemoryCardFileEntry* const entry = it->second.entry; const u32 clusterNumber = it->second.consecutiveCluster; - wxFFile* file = m_lastAccessedFile.ReOpen( m_folderName, &it->second, L"r+b", true ); - if ( file->IsOpened() ) { - const u32 clusterOffset = ( page % 2 ) * PageSize + offset; - const u32 fileSize = entry->entry.data.length; - const u32 fileOffsetStart = std::min( clusterNumber * ClusterSize + clusterOffset, fileSize ); - const u32 fileOffsetEnd = std::min( fileOffsetStart + dataLength, fileSize ); - const u32 bytesToWrite = fileOffsetEnd - fileOffsetStart; + + if ( m_performFileWrites ) { + wxFFile* file = m_lastAccessedFile.ReOpen( m_folderName, &it->second, L"r+b", true ); + if ( file->IsOpened() ) { + const u32 clusterOffset = ( page % 2 ) * PageSize + offset; + const u32 fileSize = entry->entry.data.length; + const u32 fileOffsetStart = std::min( clusterNumber * ClusterSize + clusterOffset, fileSize ); + const u32 fileOffsetEnd = std::min( fileOffsetStart + dataLength, fileSize ); + const u32 bytesToWrite = fileOffsetEnd - fileOffsetStart; - wxFileOffset actualFileSize = file->Length(); - if ( actualFileSize < fileOffsetStart ) { - file->Seek( actualFileSize ); - const u32 diff = fileOffsetStart - actualFileSize; - u8 temp = 0xFF; - for ( u32 i = 0; i < diff; ++i ) { - file->Write( &temp, 1 ); + wxFileOffset actualFileSize = file->Length(); + if ( actualFileSize < fileOffsetStart ) { + file->Seek( actualFileSize ); + const u32 diff = fileOffsetStart - actualFileSize; + u8 temp = 0xFF; + for ( u32 i = 0; i < diff; ++i ) { + file->Write( &temp, 1 ); + } } - } - const wxFileOffset fileOffset = file->Tell(); - if ( fileOffset != fileOffsetStart ) { - file->Seek( fileOffsetStart ); + const wxFileOffset fileOffset = file->Tell(); + if ( fileOffset != fileOffsetStart ) { + file->Seek( fileOffsetStart ); + } + if ( bytesToWrite > 0 ) { + file->Write( src, bytesToWrite ); + } + } else { + return false; } - if ( bytesToWrite > 0 ) { - file->Write( src, bytesToWrite ); - } - } else { - return false; } return true; diff --git a/pcsx2/gui/MemoryCardFolder.h b/pcsx2/gui/MemoryCardFolder.h index 028a0bcd3..421f088df 100644 --- a/pcsx2/gui/MemoryCardFolder.h +++ b/pcsx2/gui/MemoryCardFolder.h @@ -283,6 +283,9 @@ protected: bool m_isEnabled; + // if set to false, nothing is actually written to the file system while flushing, and data is discarded instead + bool m_performFileWrites; + public: FolderMemoryCard(); virtual ~FolderMemoryCard() throw() {} @@ -293,7 +296,7 @@ public: // Initialize & Load Memory Card with values configured in the Memory Card Manager void Open( const bool enableFiltering, const wxString& filter ); // Initialize & Load Memory Card with provided custom values - void Open( const wxString& fullPath, const AppConfig::McdOptions& mcdOptions, const u32 sizeInClusters, const bool enableFiltering, const wxString& filter ); + void Open( const wxString& fullPath, const AppConfig::McdOptions& mcdOptions, const u32 sizeInClusters, const bool enableFiltering, const wxString& filter, bool simulateFileWrites = false ); // Close the memory card and flush changes to the file system. Set flush to false to not store changes. void Close( bool flush = true );