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.
This commit is contained in:
Admiral H. Curtiss 2014-11-10 15:39:40 +01:00
parent 875be67a7e
commit 1d46800888
15 changed files with 1337 additions and 29 deletions

View File

@ -1104,7 +1104,12 @@ typedef struct _PS2E_ComponentAPI_Mcd
u64 (PS2E_CALLBACK* McdGetCRC)( PS2E_THISPTR thisptr, uint port, uint slot ); 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; } PS2E_ComponentAPI_Mcd;

View File

@ -296,6 +296,7 @@ set(pcsx2GuiSources
gui/MainFrame.cpp gui/MainFrame.cpp
gui/MainMenuClicks.cpp gui/MainMenuClicks.cpp
gui/MemoryCardFile.cpp gui/MemoryCardFile.cpp
gui/MemoryCardFolder.cpp
gui/Panels/BaseApplicableConfigPanel.cpp gui/Panels/BaseApplicableConfigPanel.cpp
gui/Panels/MemoryCardListPanel.cpp gui/Panels/MemoryCardListPanel.cpp
gui/MessageBoxes.cpp gui/MessageBoxes.cpp
@ -348,6 +349,7 @@ set(pcsx2GuiHeaders
gui/IsoDropTarget.h gui/IsoDropTarget.h
gui/MainFrame.h gui/MainFrame.h
gui/MemoryCardFile.h gui/MemoryCardFile.h
gui/MemoryCardFolder.h
gui/MSWstuff.h gui/MSWstuff.h
gui/Panels/ConfigurationPanels.h gui/Panels/ConfigurationPanels.h
gui/Panels/LogOptionsPanels.h gui/Panels/LogOptionsPanels.h

View File

@ -29,6 +29,8 @@
#include "ps2/HwInternal.h" #include "ps2/HwInternal.h"
#include "Sio.h"
using namespace Threading; using namespace Threading;
extern u8 psxhblankgate; extern u8 psxhblankgate;
@ -428,6 +430,12 @@ static __fi void VSyncEnd(u32 sCycle)
hwIntcIrq(INTC_VBLANK_E); // HW Irq hwIntcIrq(INTC_VBLANK_E); // HW Irq
psxVBlankEnd(); // psxCounters vBlank End psxVBlankEnd(); // psxCounters vBlank End
if (gates) rcntEndGate(true, sCycle); // Counters End Gate Code 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 frameLimit(); // limit FPS
//Do this here, breaks Dynasty Warriors otherwise. //Do this here, breaks Dynasty Warriors otherwise.

View File

@ -66,6 +66,9 @@ u64 SysPluginBindings::McdGetCRC( uint port, uint slot )
return Mcd->McdGetCRC( (PS2E_THISPTR) Mcd, port, 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. :) // Yay, order of this array shouldn't be important. :)

View File

@ -241,6 +241,7 @@ public:
void McdSave( uint port, uint slot, const u8 *src, u32 adr, int size ); void McdSave( uint port, uint slot, const u8 *src, u32 adr, int size );
void McdEraseBlock( uint port, uint slot, u32 adr ); void McdEraseBlock( uint port, uint slot, u32 adr );
u64 McdGetCRC( uint port, uint slot ); u64 McdGetCRC( uint port, uint slot );
void McdNextFrame( uint port, uint slot );
friend class SysCorePlugins; friend class SysCorePlugins;
}; };

View File

@ -871,6 +871,14 @@ void SIODMAWrite(u8 value)
sioWrite8inl(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() void SaveStateBase::sioFreeze()
{ {
// CRCs for memory cards. // CRCs for memory cards.

View File

@ -19,6 +19,8 @@
// Games are highly unlikely to need timed IRQ's for PAD and MemoryCard handling anyway (rama). // Games are highly unlikely to need timed IRQ's for PAD and MemoryCard handling anyway (rama).
#define SIO_INLINE_IRQS #define SIO_INLINE_IRQS
#include "MemoryCardFile.h"
struct _mcd struct _mcd
{ {
u8 term; // terminator value; u8 term; // terminator value;
@ -80,6 +82,10 @@ struct _mcd
{ {
return SysPlugins.McdGetCRC(port, slot); return SysPlugins.McdGetCRC(port, slot);
} }
void NextFrame() {
SysPlugins.McdNextFrame( port, slot );
}
}; };
struct _sio struct _sio
@ -117,3 +123,4 @@ extern void sioWriteCtrl16(u16 value);
extern void sioInterrupt(); extern void sioInterrupt();
extern void InitializeSIO(u8 value); extern void InitializeSIO(u8 value);
extern void SetForceMcdEjectTimeoutNow(); extern void SetForceMcdEjectTimeoutNow();
extern void sioNextFrame();

View File

@ -16,8 +16,8 @@
#include "PrecompiledHeader.h" #include "PrecompiledHeader.h"
#include "Utilities/SafeArray.inl" #include "Utilities/SafeArray.inl"
#include <wx/file.h> #include <wx/file.h>
#include <wx/dir.h>
#include "MemoryCardFile.h" #include <wx/stopwatch.h>
// IMPORTANT! If this gets a macro redefinition error it means PluginCallbacks.h is included // 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 // 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; struct Component_FileMcd;
#define PS2E_THISPTR Component_FileMcd* #define PS2E_THISPTR Component_FileMcd*
#include "MemoryCardFile.h"
#include "MemoryCardFolder.h"
#include "System.h" #include "System.h"
#include "AppConfig.h" #include "AppConfig.h"
#include "svnrev.h" #include "svnrev.h"
#include <wx/ffile.h> #include <wx/ffile.h>
#include <map>
static const int MCD_SIZE = 1024 * 8 * 16; // Legacy PSX card default size 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 struct Component_FileMcd
{ {
PS2E_ComponentAPI_Mcd api; // callbacks the plugin provides back to the emulator 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 FileMemoryCard impl; // class-based implementations we refer to when API is invoked
#endif
Component_FileMcd(); 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 ) ); 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() Component_FileMcd::Component_FileMcd()
{ {
memzero( api ); memzero( api );
@ -475,6 +489,7 @@ Component_FileMcd::Component_FileMcd()
api.McdSave = FileMcd_Save; api.McdSave = FileMcd_Save;
api.McdEraseBlock = FileMcd_EraseBlock; api.McdEraseBlock = FileMcd_EraseBlock;
api.McdGetCRC = FileMcd_GetCRC; 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; 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. //Tests if a string is a valid name for a new file within a specified directory.
//returns true if: //returns true if:
// - the file name has a minimum length of minNumCharacters chars (default is 5 chars: at least 1 char + '.' + 3-chars extension) // - the file name has a minimum length of minNumCharacters chars (default is 5 chars: at least 1 char + '.' + 3-chars extension)

View File

@ -15,6 +15,9 @@
#pragma once #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 // 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 // memorycard system is properly extracted into a plugin system (which would make it a
// separate project file). // separate project file).

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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();
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <wx/file.h>
#include <wx/dir.h>
#include <wx/stopwatch.h>
#include <wx/ffile.h>
#include <map>
#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<u32, MemoryCardFileEntryCluster> m_fileEntryDict;
// holds a copy of modified areas of the memory card, in page-sized chunks
std::map<u32, MemoryCardPage> 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 );
};

View File

@ -636,6 +636,7 @@
<ClCompile Include="..\..\gui\MainFrame.cpp" /> <ClCompile Include="..\..\gui\MainFrame.cpp" />
<ClCompile Include="..\..\gui\MainMenuClicks.cpp" /> <ClCompile Include="..\..\gui\MainMenuClicks.cpp" />
<ClCompile Include="..\..\gui\MemoryCardFile.cpp" /> <ClCompile Include="..\..\gui\MemoryCardFile.cpp" />
<ClCompile Include="..\..\gui\MemoryCardFolder.cpp" />
<ClCompile Include="..\..\gui\MessageBoxes.cpp" /> <ClCompile Include="..\..\gui\MessageBoxes.cpp" />
<ClCompile Include="..\..\gui\MSWstuff.cpp" /> <ClCompile Include="..\..\gui\MSWstuff.cpp" />
<ClCompile Include="..\..\gui\pxLogTextCtrl.cpp" /> <ClCompile Include="..\..\gui\pxLogTextCtrl.cpp" />

View File

@ -635,6 +635,9 @@
<ClCompile Include="..\..\gui\MemoryCardFile.cpp"> <ClCompile Include="..\..\gui\MemoryCardFile.cpp">
<Filter>AppHost</Filter> <Filter>AppHost</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\gui\MemoryCardFolder.cpp">
<Filter>AppHost</Filter>
</ClCompile>
<ClCompile Include="..\..\gui\MessageBoxes.cpp"> <ClCompile Include="..\..\gui\MessageBoxes.cpp">
<Filter>AppHost</Filter> <Filter>AppHost</Filter>
</ClCompile> </ClCompile>

View File

@ -636,6 +636,7 @@
<ClCompile Include="..\..\gui\MainFrame.cpp" /> <ClCompile Include="..\..\gui\MainFrame.cpp" />
<ClCompile Include="..\..\gui\MainMenuClicks.cpp" /> <ClCompile Include="..\..\gui\MainMenuClicks.cpp" />
<ClCompile Include="..\..\gui\MemoryCardFile.cpp" /> <ClCompile Include="..\..\gui\MemoryCardFile.cpp" />
<ClCompile Include="..\..\gui\MemoryCardFolder.cpp" />
<ClCompile Include="..\..\gui\MessageBoxes.cpp" /> <ClCompile Include="..\..\gui\MessageBoxes.cpp" />
<ClCompile Include="..\..\gui\MSWstuff.cpp" /> <ClCompile Include="..\..\gui\MSWstuff.cpp" />
<ClCompile Include="..\..\gui\pxLogTextCtrl.cpp" /> <ClCompile Include="..\..\gui\pxLogTextCtrl.cpp" />

View File

@ -635,6 +635,9 @@
<ClCompile Include="..\..\gui\MemoryCardFile.cpp"> <ClCompile Include="..\..\gui\MemoryCardFile.cpp">
<Filter>AppHost</Filter> <Filter>AppHost</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\gui\MemoryCardFolder.cpp">
<Filter>AppHost</Filter>
</ClCompile>
<ClCompile Include="..\..\gui\MessageBoxes.cpp"> <ClCompile Include="..\..\gui\MessageBoxes.cpp">
<Filter>AppHost</Filter> <Filter>AppHost</Filter>
</ClCompile> </ClCompile>