diff --git a/common/include/PluginCallbacks.h b/common/include/PluginCallbacks.h index 744a718556..ea2ff18596 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 351fd016ef..9a958ce20a 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 1eb8557caa..cc9c1c6a31 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 28b8a23032..46802ff867 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 87f9505f34..5ed768c8a2 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 f00137cce3..aa6eac6881 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 dc482336b3..c57f7c0491 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 7c3cd2bd20..e9dfd74862 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 9f27c3f93d..387d6887b2 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 0000000000..5ac35e92d7 --- /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 0000000000..dfdc769e47 --- /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 af0c8edad9..1e469df160 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 f8a6210b0b..80e1532c21 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 b9b4b04323..5d3c284c3c 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 6c97171bb8..065beb5dad 100644 --- a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters +++ b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters @@ -635,6 +635,9 @@ AppHost + + AppHost + AppHost