Merge pull request #359 from AdmiralCurtiss/memcard-folder-cache

Memory Card as folder support by AdmiralCurtiss
This commit is contained in:
refractionpcsx2 2015-07-20 23:15:59 +01:00
commit 539a1767a3
28 changed files with 3164 additions and 132 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1104,7 +1104,14 @@ 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 (PS2E_CALLBACK* McdReIndex)( PS2E_THISPTR thisptr, uint port, uint slot, const wxString& filter );
void* reserved[6];
} PS2E_ComponentAPI_Mcd;

View File

@ -272,6 +272,7 @@ set(pcsx2GuiSources
gui/Panels/ThemeSelectorPanel.cpp
gui/Dialogs/BaseConfigurationDialog.cpp
gui/Dialogs/ConfirmationDialogs.cpp
gui/Dialogs/ConvertMemoryCardDialog.cpp
gui/Dialogs/CreateMemoryCardDialog.cpp
gui/Dialogs/FirstTimeWizard.cpp
gui/Dialogs/GameDatabaseDialog.cpp
@ -296,6 +297,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 +350,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

View File

@ -441,6 +441,7 @@ struct Pcsx2Config
BackupSavestate :1,
// enables simulated ejection of memory cards when loading savestates
McdEnableEjection :1,
McdFolderAutoManage :1,
MultitapPort0_Enabled:1,
MultitapPort1_Enabled:1,

View File

@ -29,6 +29,8 @@
#include "ps2/HwInternal.h"
#include "Sio.h"
using namespace Threading;
extern u8 psxhblankgate;
@ -428,6 +430,10 @@ 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
// FolderMemoryCard needs information on how much time has passed since the last write
sioNextFrame();
frameLimit(); // limit FPS
//Do this here, breaks Dynasty Warriors otherwise.

View File

@ -398,6 +398,7 @@ Pcsx2Config::Pcsx2Config()
bitset = 0;
// Set defaults for fresh installs / reset settings
McdEnableEjection = true;
McdFolderAutoManage = true;
EnablePatches = true;
BackupSavestate = true;
}
@ -417,6 +418,7 @@ void Pcsx2Config::LoadSave( IniInterface& ini )
IniBitBool( BackupSavestate );
IniBitBool( McdEnableEjection );
IniBitBool( McdFolderAutoManage );
IniBitBool( MultitapPort0_Enabled );
IniBitBool( MultitapPort1_Enabled );

View File

@ -66,6 +66,13 @@ 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 );
}
void SysPluginBindings::McdReIndex( uint port, uint slot, const wxString& filter ) {
Mcd->McdReIndex( (PS2E_THISPTR) Mcd, port, slot, filter );
}
// ----------------------------------------------------------------------------
// Yay, order of this array shouldn't be important. :)

View File

@ -241,6 +241,8 @@ 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 );
void McdReIndex( uint port, uint slot, const wxString& filter );
friend class SysCorePlugins;
};

View File

@ -60,6 +60,13 @@ void SetForceMcdEjectTimeoutNow()
mcds[port][slot].ForceEjection_Timeout = FORCED_MCD_EJECTION_MAX_TRIES;
}
void ClearMcdEjectTimeoutNow()
{
for( u8 port=0; port<2; ++port )
for( u8 slot=0; slot<4; ++slot )
mcds[port][slot].ForceEjection_Timeout = 0;
}
// SIO Inline'd IRQs : Calls the SIO interrupt handlers directly instead of
// feeding them through the IOP's branch test. (see SIO.H for details)
@ -871,6 +878,28 @@ 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();
}
}
}
// Used to figure out when a new game boots, so that memory cards can re-index themselves and only load data relevant to that game.
wxString SioCurrentGameSerial = L"";
void sioSetGameSerial( const wxString& serial ) {
if ( serial == SioCurrentGameSerial ) { return; }
SioCurrentGameSerial = serial;
for ( uint port = 0; port < 2; ++port ) {
for ( uint slot = 0; slot < 4; ++slot ) {
mcds[port][slot].ReIndex( serial );
}
}
SetForceMcdEjectTimeoutNow();
}
void SaveStateBase::sioFreeze()
{
// CRCs for memory cards.

View File

@ -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,14 @@ struct _mcd
{
return SysPlugins.McdGetCRC(port, slot);
}
void NextFrame() {
SysPlugins.McdNextFrame( port, slot );
}
void ReIndex(const wxString& filter = L"") {
SysPlugins.McdReIndex( port, slot, filter );
}
};
struct _sio
@ -117,3 +127,6 @@ extern void sioWriteCtrl16(u16 value);
extern void sioInterrupt();
extern void InitializeSIO(u8 value);
extern void SetForceMcdEjectTimeoutNow();
extern void ClearMcdEjectTimeoutNow();
extern void sioNextFrame();
extern void sioSetGameSerial(const wxString& serial);

View File

@ -16,6 +16,9 @@
#include "PrecompiledHeader.h"
#include "AsciiFile.h"
#include <wx/dir.h>
#include <wx/string.h>
void AsciiFile::Printf( const char* fmt, ... )
{
va_list list;
@ -25,3 +28,70 @@ void AsciiFile::Printf( const char* fmt, ... )
va_end( list );
Write( ascii, strlen(ascii) );
}
bool CopyDirectory( const wxString& from, const wxString& to ) {
wxDir src( from );
if ( !src.IsOpened() ) {
return false;
}
wxMkdir( to );
wxDir dst( to );
if ( !dst.IsOpened() ) {
return false;
}
wxString filename;
// copy directories
if ( src.GetFirst( &filename, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN ) ) {
do {
if ( !CopyDirectory( wxFileName( from, filename ).GetFullPath(), wxFileName( to, filename ).GetFullPath() ) ) {
return false;
}
} while ( src.GetNext( &filename ) );
}
// copy files
if ( src.GetFirst( &filename, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN ) ) {
do {
if ( !wxCopyFile( wxFileName( from, filename ).GetFullPath(), wxFileName( to, filename ).GetFullPath() ) ) {
return false;
}
} while ( src.GetNext( &filename ) );
}
return true;
}
bool RemoveDirectory( const wxString& dirname ) {
{
wxDir dir( dirname );
if ( !dir.IsOpened() ) {
return false;
}
wxString filename;
// delete subdirs recursively
if ( dir.GetFirst( &filename, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN ) ) {
do {
if ( !RemoveDirectory( wxFileName( dirname, filename ).GetFullPath() ) ) {
return false;
}
} while ( dir.GetNext( &filename ) );
}
// delete files
if ( dir.GetFirst( &filename, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN ) ) {
do {
if ( !wxRemoveFile( wxFileName( dirname, filename ).GetFullPath() ) ) {
return false;
}
} while ( dir.GetNext( &filename ) );
}
}
// oddly enough this has different results compared to the more sensible dirname.Rmdir(), don't change!
return wxFileName::Rmdir( dirname );
}

View File

@ -611,6 +611,10 @@ void AppConfig::LoadSaveMemcards( IniInterface& ini )
Mcd[slot].Enabled, Mcd[slot].Enabled );
ini.Entry( pxsFmt( L"Slot%u_Filename", slot+1 ),
Mcd[slot].Filename, Mcd[slot].Filename );
int type = (int)Mcd[slot].Type;
ini.Entry( pxsFmt( L"Slot%u_Type", slot + 1 ),
type, (int)MemoryCardType::MemoryCard_File );
Mcd[slot].Type = (MemoryCardType)type;
}
for( uint slot=2; slot<8; ++slot )
@ -622,6 +626,10 @@ void AppConfig::LoadSaveMemcards( IniInterface& ini )
Mcd[slot].Enabled, Mcd[slot].Enabled );
ini.Entry( pxsFmt( L"Multitap%u_Slot%u_Filename", mtport, mtslot ),
Mcd[slot].Filename, Mcd[slot].Filename );
int type = (int)Mcd[slot].Type;
ini.Entry( pxsFmt( L"Multitap%u_Slot%u_Type", mtport, mtslot ),
type, (int)MemoryCardType::MemoryCard_File );
Mcd[slot].Type = (MemoryCardType)type;
}
}

View File

@ -98,6 +98,14 @@ enum AspectRatioType
AspectRatio_MaxCount
};
enum MemoryCardType
{
MemoryCard_None,
MemoryCard_File,
MemoryCard_Folder,
MemoryCard_MaxCount
};
// =====================================================================================================
// Pcsx2 Application Configuration.
// =====================================================================================================
@ -182,6 +190,7 @@ public:
{
wxFileName Filename; // user-configured location of this memory card
bool Enabled; // memory card enabled (if false, memcard will not show up in-game)
MemoryCardType Type; // the memory card implementation that should be used
};
// ------------------------------------------------------------------------

View File

@ -31,6 +31,7 @@
#include "Elfheader.h"
#include "Patch.h"
#include "R5900Exceptions.h"
#include "Sio.h"
__aligned16 SysMtgsThread mtgsThread;
__aligned16 AppCoreThread CoreThread;
@ -344,6 +345,7 @@ void AppCoreThread::ApplySettings( const Pcsx2Config& src )
wxString gameName;
wxString gameCompat;
wxString gameMemCardFilter;
int numberLoadedCheats;
int numberLoadedWideScreenPatches;
@ -368,6 +370,7 @@ void AppCoreThread::ApplySettings( const Pcsx2Config& src )
gameName = game.getString("Name");
gameName += L" (" + game.getString("Region") + L")";
gameCompat = L" [Status = "+compatToStringWX(compat)+L"]";
gameMemCardFilter = game.getString("MemCardFilter");
}
if (EmuConfig.EnablePatches) {
@ -382,6 +385,12 @@ void AppCoreThread::ApplySettings( const Pcsx2Config& src )
}
}
if (!gameMemCardFilter.IsEmpty()) {
sioSetGameSerial(gameMemCardFilter);
} else {
sioSetGameSerial(curGameKey);
}
if (gameName.IsEmpty() && gameSerial.IsEmpty() && gameCRC.IsEmpty())
{
// if all these conditions are met, it should mean that we're currently running BIOS code.
@ -513,6 +522,7 @@ void AppCoreThread::GameStartingInThread()
m_ExecMode = ExecMode_Paused;
OnResumeReady();
_reset_stuff_as_needed();
ClearMcdEjectTimeoutNow(); // probably safe to do this when a game boots, eliminates annoying prompts
m_ExecMode = ExecMode_Opened;
_parent::GameStartingInThread();

View File

@ -237,4 +237,26 @@ namespace Dialogs
void CreateControls();
void OnOk_Click( wxCommandEvent& evt );
};
// --------------------------------------------------------------------------------------
// ConvertMemoryCardDialog
// --------------------------------------------------------------------------------------
class ConvertMemoryCardDialog : public wxDialogWithHelpers
{
protected:
wxDirName m_mcdPath;
wxString m_mcdSourceFilename;
wxTextCtrl* m_text_filenameInput;
pxRadioPanel* m_radio_CardType;
public:
virtual ~ConvertMemoryCardDialog() throw() {}
ConvertMemoryCardDialog( wxWindow* parent, const wxDirName& mcdPath, const AppConfig::McdOptions& mcdSourceConfig );
protected:
void CreateControls( const MemoryCardType sourceType );
void OnOk_Click( wxCommandEvent& evt );
bool ConvertToFile( const wxFileName& sourcePath, const wxFileName& targetPath, const u32 sizeInMB );
bool ConvertToFolder( const wxFileName& sourcePath, const wxFileName& targetPath );
};
}

View File

@ -0,0 +1,236 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2015 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "ConfigurationDialog.h"
#include "System.h"
#include "MemoryCardFile.h"
#include "MemoryCardFolder.h"
#include <wx/ffile.h>
enum MemoryCardConversionType {
MemoryCardConversion_File_8MB,
MemoryCardConversion_File_16MB,
MemoryCardConversion_File_32MB,
MemoryCardConversion_File_64MB,
MemoryCardConversion_Folder,
MemoryCardConversion_MaxCount
};
Dialogs::ConvertMemoryCardDialog::ConvertMemoryCardDialog( wxWindow* parent, const wxDirName& mcdPath, const AppConfig::McdOptions& mcdSourceConfig )
: wxDialogWithHelpers( parent, _( "Convert a memory card to a different format" ) )
, m_mcdPath( mcdPath )
, m_mcdSourceFilename( mcdSourceConfig.Filename.GetFullName() )
{
SetMinWidth( 472 );
CreateControls( mcdSourceConfig.Type );
if ( m_radio_CardType ) m_radio_CardType->Realize();
wxBoxSizer& s_buttons( *new wxBoxSizer( wxHORIZONTAL ) );
s_buttons += new wxButton( this, wxID_OK, _( "Convert" ) ) | pxProportion( 2 );
s_buttons += pxStretchSpacer( 3 );
s_buttons += new wxButton( this, wxID_CANCEL ) | pxProportion( 2 );
wxBoxSizer& s_padding( *new wxBoxSizer( wxVERTICAL ) );
s_padding += Heading( wxString( _( "Convert: " ) ) + ( mcdPath + m_mcdSourceFilename ).GetFullPath() ).Unwrapped() | pxSizerFlags::StdExpand();
wxBoxSizer& s_filename( *new wxBoxSizer( wxHORIZONTAL ) );
s_filename += Heading( _( "To: " ) ).SetMinWidth( 50 );
m_text_filenameInput->SetMinSize( wxSize( 250, 20 ) );
m_text_filenameInput->SetValue( wxFileName( m_mcdSourceFilename ).GetName() + L"_converted" );
s_filename += m_text_filenameInput;
s_filename += Heading( L".ps2" );
s_padding += s_filename | wxALIGN_LEFT;
s_padding += m_radio_CardType | pxSizerFlags::StdExpand();
if ( mcdSourceConfig.Type != MemoryCardType::MemoryCard_File ) {
s_padding += Heading( pxE( L"Please note that the resulting file may not actually contain all saves, depending on how many are in the source memory card." ) );
}
s_padding += Heading( pxE( L"WARNING: Converting a memory card may take a while! Please do not close the emulator during the conversion process, even if the emulator is no longer responding to input." ) );
s_padding += 12;
s_padding += s_buttons | pxSizerFlags::StdCenter();
*this += s_padding | pxSizerFlags::StdExpand();
Connect( wxID_OK, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ConvertMemoryCardDialog::OnOk_Click ) );
Connect( m_text_filenameInput->GetId(), wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( ConvertMemoryCardDialog::OnOk_Click ) );
m_text_filenameInput->SetFocus();
m_text_filenameInput->SelectAll();
}
void Dialogs::ConvertMemoryCardDialog::CreateControls( const MemoryCardType sourceType ) {
m_text_filenameInput = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER );
RadioPanelItem toFile8MB = RadioPanelItem( _( "8MB File" ), pxE( L"Convert this memory card to a standard 8 MB Memory Card .ps2 file." ) )
.SetInt( MemoryCardConversionType::MemoryCardConversion_File_8MB );
RadioPanelItem toFile16MB = RadioPanelItem( _( "16MB File" ), pxE( L"Convert this memory card to a 16 MB Memory Card .ps2 file." ) )
.SetInt( MemoryCardConversionType::MemoryCardConversion_File_16MB );
RadioPanelItem toFile32MB = RadioPanelItem( _( "32MB File" ), pxE( L"Convert this memory card to a 32 MB Memory Card .ps2 file." ) )
.SetInt( MemoryCardConversionType::MemoryCardConversion_File_32MB );
RadioPanelItem toFile64MB = RadioPanelItem( _( "64MB File" ), pxE( L"Convert this memory card to a 64 MB Memory Card .ps2 file." ) )
.SetInt( MemoryCardConversionType::MemoryCardConversion_File_64MB );
RadioPanelItem toFolder = RadioPanelItem( _( "Folder" ), _( "Convert this memory card to a folder of individual saves." ) )
.SetInt( MemoryCardConversionType::MemoryCardConversion_Folder );
const RadioPanelItem tblForFile[] = { toFolder };
const RadioPanelItem tblForFolder[] = { toFile8MB, toFile16MB, toFile32MB, toFile64MB };
switch ( sourceType ) {
case MemoryCardType::MemoryCard_File:
m_radio_CardType = new pxRadioPanel( this, tblForFile );
break;
case MemoryCardType::MemoryCard_Folder:
m_radio_CardType = new pxRadioPanel( this, tblForFolder );
break;
default:
Console.Error( L"Memory Card Conversion: Invalid source type!" );
return;
}
m_radio_CardType->SetDefaultItem( 0 );
}
void Dialogs::ConvertMemoryCardDialog::OnOk_Click( wxCommandEvent& evt ) {
wxString composedName = m_text_filenameInput->GetValue().Trim() + L".ps2";
wxString errMsg;
if ( !isValidNewFilename( composedName, m_mcdPath, errMsg, 5 ) ) {
wxString message;
message.Printf( _( "Error (%s)" ), errMsg.c_str() );
Msgbox::Alert( message, _( "Convert memory card" ) );
m_text_filenameInput->SetFocus();
m_text_filenameInput->SelectAll();
return;
}
bool success = false;
wxFileName sourcePath = ( m_mcdPath + m_mcdSourceFilename );
wxFileName targetPath = ( m_mcdPath + composedName );
if ( m_radio_CardType ) {
MemoryCardConversionType targetType = (MemoryCardConversionType)m_radio_CardType->SelectedItem().SomeInt;
switch ( targetType ) {
case MemoryCardConversionType::MemoryCardConversion_File_8MB:
success = ConvertToFile( sourcePath, targetPath, 8 );
break;
case MemoryCardConversionType::MemoryCardConversion_File_16MB:
success = ConvertToFile( sourcePath, targetPath, 16 );
break;
case MemoryCardConversionType::MemoryCardConversion_File_32MB:
success = ConvertToFile( sourcePath, targetPath, 32 );
break;
case MemoryCardConversionType::MemoryCardConversion_File_64MB:
success = ConvertToFile( sourcePath, targetPath, 64 );
break;
case MemoryCardConversionType::MemoryCardConversion_Folder:
success = ConvertToFolder( sourcePath, targetPath );
break;
default:
Msgbox::Alert( _( "This target type is not supported!" ), _( "Convert memory card" ) );
return;
}
}
if ( !success ) {
Msgbox::Alert( _( "Memory Card conversion failed for unknown reasons." ), _( "Convert memory card" ) );
return;
}
EndModal( wxID_OK );
}
bool Dialogs::ConvertMemoryCardDialog::ConvertToFile( const wxFileName& sourcePath, const wxFileName& targetPath, const u32 sizeInMB ) {
// Conversion method: Open FolderMcd as usual, then read the raw data from it and write it to a file stream
wxFFile targetFile( targetPath.GetFullPath(), L"wb" );
if ( !targetFile.IsOpened() ) {
return false;
}
FolderMemoryCard sourceFolderMemoryCard;
AppConfig::McdOptions config;
config.Enabled = true;
config.Type = MemoryCardType::MemoryCard_Folder;
sourceFolderMemoryCard.Open( sourcePath.GetFullPath(), config, ( sizeInMB * 1024 * 1024 ) / FolderMemoryCard::ClusterSize, false, L"" );
u8 buffer[FolderMemoryCard::PageSizeRaw];
u32 adr = 0;
while ( adr < sourceFolderMemoryCard.GetSizeInClusters() * FolderMemoryCard::ClusterSizeRaw ) {
sourceFolderMemoryCard.Read( buffer, adr, FolderMemoryCard::PageSizeRaw );
targetFile.Write( buffer, FolderMemoryCard::PageSizeRaw );
adr += FolderMemoryCard::PageSizeRaw;
}
targetFile.Close();
sourceFolderMemoryCard.Close( false );
return true;
}
bool Dialogs::ConvertMemoryCardDialog::ConvertToFolder( const wxFileName& sourcePath, const wxFileName& targetPath ) {
// Conversion method: Read all pages of the FileMcd into a FolderMcd, then just write that out with the regular methods
wxFFile sourceFile( sourcePath.GetFullPath(), L"rb" );
if ( !sourceFile.IsOpened() ) {
return false;
}
u8 buffer[FolderMemoryCard::PageSizeRaw];
FolderMemoryCard targetFolderMemoryCard;
AppConfig::McdOptions config;
config.Enabled = true;
config.Type = MemoryCardType::MemoryCard_Folder;
u32 adr = 0;
for ( int i = 0; i < 2; ++i ) {
// Before writing the data, we first simulate the entire process without actual writes to the file system.
// This ensures that if we crash/fail due to a corrupted memory card file system or similar, we do so during
// the simulation run, and don't actually write out any partial data to the host file system.
bool simulateWrites = i == 0;
targetFolderMemoryCard.Open( targetPath.GetFullPath(), config, 0, false, L"", simulateWrites );
adr = 0;
sourceFile.Seek( 0 );
while ( !sourceFile.Eof() ) {
int size = sourceFile.Read( buffer, FolderMemoryCard::PageSizeRaw );
if ( size > 0 ) {
targetFolderMemoryCard.Save( buffer, adr, size );
adr += size;
}
}
targetFolderMemoryCard.Close();
}
sourceFile.Close();
if ( adr != FolderMemoryCard::TotalSizeRaw ) {
// reset memory card metrics in superblock to the default 8MB, since the converted card was different
targetFolderMemoryCard.Open( targetPath.GetFullPath(), config, 0, true, L"" );
targetFolderMemoryCard.SetSizeInMB( 8 );
targetFolderMemoryCard.Close();
}
return true;
}

View File

@ -153,17 +153,31 @@ void Dialogs::CreateMemoryCardDialog::OnOk_Click( wxCommandEvent& evt )
return;
}
wxString fullPath=(m_mcdpath + composedName).GetFullPath();
if( !CreateIt(
fullPath,
m_radio_CardSize ? m_radio_CardSize->SelectedItem().SomeInt : 8
) )
{
Msgbox::Alert(
_("Error: The memory card could not be created."),
_("Create memory card")
);
return;
wxString fullPath = ( m_mcdpath + composedName ).GetFullPath();
if ( m_radio_CardSize && m_radio_CardSize->SelectedItem().SomeInt == 0 ) {
// user selected to create a folder memory card
if ( !wxFileName::Mkdir( fullPath ) ) {
Msgbox::Alert(
_( "Error: The directory for the memory card could not be created." ),
_( "Create memory card" )
);
} else {
// also create an empty superblock so we can recognize memory card folders based on if they have a superblock
wxFFile superblock( wxFileName( fullPath, L"_pcsx2_superblock" ).GetFullPath(), L"wb" );
superblock.Close();
}
} else {
// otherwise create a file
if ( !CreateIt(
fullPath,
m_radio_CardSize ? m_radio_CardSize->SelectedItem().SomeInt : 8
) ) {
Msgbox::Alert(
_( "Error: The memory card could not be created." ),
_( "Create memory card" )
);
return;
}
}
result_createdMcdFilename = composedName;
@ -205,7 +219,11 @@ void Dialogs::CreateMemoryCardDialog::CreateControls()
RadioPanelItem(_("64 MB"), _("Low compatibility warning: Yes it's very big, but may not work with many games."))
. SetToolTip(_t("Use at your own risk. Erratic memory card behavior is possible (though unlikely)."))
. SetInt(64)
. SetInt(64),
RadioPanelItem(_("Folder [experimental]"), _("Store memory card contents in the host filesystem instead of a file."))
. SetToolTip(_t("Automatically manages memory card contents so that the console only sees files related to the currently running software. Allows you to drag-and-drop files in and out of the memory card with your standard file explorer. This is still experimental, so use at your own risk!"))
. SetInt(0)
};
m_radio_CardSize = new pxRadioPanel( this, tbl_CardSizes );

View File

@ -40,6 +40,12 @@ Panels::McdConfigPanel_Toggles::McdConfigPanel_Toggles(wxWindow *parent)
)
);
m_folderAutoIndex = new pxCheckBox( this,
_( "Automatically manage saves based on running game" ),
pxE( L"(Folder type only) Re-index memory card content every time the running software changes. This prevents the memory card from running out of space for saves."
)
);
//m_check_SavestateBackup = new pxCheckBox( this, pxsFmt(_("Backup existing Savestate when creating a new one")) );
/*
for( uint i=0; i<2; ++i )
@ -64,6 +70,7 @@ Panels::McdConfigPanel_Toggles::McdConfigPanel_Toggles(wxWindow *parent)
*this += new wxStaticLine( this ) | StdExpand();
*this += m_check_Ejection;
*this += m_folderAutoIndex;
}
void Panels::McdConfigPanel_Toggles::Apply()
@ -73,6 +80,7 @@ void Panels::McdConfigPanel_Toggles::Apply()
//g_Conf->EmuOptions.BackupSavestate = m_check_SavestateBackup->GetValue();
g_Conf->EmuOptions.McdEnableEjection = m_check_Ejection->GetValue();
g_Conf->EmuOptions.McdFolderAutoManage = m_folderAutoIndex->GetValue();
}
void Panels::McdConfigPanel_Toggles::AppStatusEvent_OnSettingsApplied()
@ -82,6 +90,7 @@ void Panels::McdConfigPanel_Toggles::AppStatusEvent_OnSettingsApplied()
//m_check_SavestateBackup ->SetValue( g_Conf->EmuOptions.BackupSavestate );
m_check_Ejection ->SetValue( g_Conf->EmuOptions.McdEnableEjection );
m_folderAutoIndex ->SetValue( g_Conf->EmuOptions.McdFolderAutoManage );
}

View File

@ -16,8 +16,8 @@
#include "PrecompiledHeader.h"
#include "Utilities/SafeArray.inl"
#include <wx/file.h>
#include "MemoryCardFile.h"
#include <wx/dir.h>
#include <wx/stopwatch.h>
// 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 <wx/ffile.h>
#include <map>
static const int MCD_SIZE = 1024 * 8 * 16; // Legacy PSX card default size
@ -167,7 +171,12 @@ void FileMemoryCard::Open()
cont = true;
}
Console.WriteLn( cont ? Color_Gray : Color_Green, L"McdSlot %u: " + str, slot );
if ( g_Conf->Mcd[slot].Type != MemoryCardType::MemoryCard_File ) {
str = L"[is not memcard file]";
cont = true;
}
Console.WriteLn( cont ? Color_Gray : Color_Green, L"McdSlot %u [File]: " + str, slot );
if( cont ) continue;
const wxULongLong fsz = fname.GetSize();
@ -403,8 +412,9 @@ u64 FileMemoryCard::GetCRC( uint slot )
struct Component_FileMcd
{
PS2E_ComponentAPI_Mcd api; // callbacks the plugin provides back to the emulator
FileMemoryCard impl; // class-based implementations we refer to when API is invoked
PS2E_ComponentAPI_Mcd api; // callbacks the plugin provides back to the emulator
FileMemoryCard impl; // class-based implementations we refer to when API is invoked
FolderMemoryCardAggregator implFolder;
Component_FileMcd();
};
@ -419,46 +429,135 @@ uint FileMcd_ConvertToSlot( uint port, uint slot )
static void PS2E_CALLBACK FileMcd_EmuOpen( PS2E_THISPTR thisptr, const PS2E_SessionInfo *session )
{
thisptr->impl.Open();
thisptr->implFolder.SetFiltering( g_Conf->EmuOptions.McdFolderAutoManage );
thisptr->implFolder.Open();
}
static void PS2E_CALLBACK FileMcd_EmuClose( PS2E_THISPTR thisptr )
{
thisptr->implFolder.Close();
thisptr->impl.Close();
}
static s32 PS2E_CALLBACK FileMcd_IsPresent( PS2E_THISPTR thisptr, uint port, uint slot )
{
return thisptr->impl.IsPresent( FileMcd_ConvertToSlot( port, slot ) );
const uint combinedSlot = FileMcd_ConvertToSlot( port, slot );
switch ( g_Conf->Mcd[combinedSlot].Type ) {
case MemoryCardType::MemoryCard_File:
return thisptr->impl.IsPresent( combinedSlot );
case MemoryCardType::MemoryCard_Folder:
return thisptr->implFolder.IsPresent( combinedSlot );
default:
return false;
}
}
static void PS2E_CALLBACK FileMcd_GetSizeInfo( PS2E_THISPTR thisptr, uint port, uint slot, PS2E_McdSizeInfo* outways )
{
thisptr->impl.GetSizeInfo( FileMcd_ConvertToSlot( port, slot ), *outways );
const uint combinedSlot = FileMcd_ConvertToSlot( port, slot );
switch ( g_Conf->Mcd[combinedSlot].Type ) {
case MemoryCardType::MemoryCard_File:
thisptr->impl.GetSizeInfo( combinedSlot, *outways );
break;
case MemoryCardType::MemoryCard_Folder:
thisptr->implFolder.GetSizeInfo( combinedSlot, *outways );
break;
default:
return;
}
}
static bool PS2E_CALLBACK FileMcd_IsPSX( PS2E_THISPTR thisptr, uint port, uint slot )
{
return thisptr->impl.IsPSX( FileMcd_ConvertToSlot( port, slot ) );
const uint combinedSlot = FileMcd_ConvertToSlot( port, slot );
switch ( g_Conf->Mcd[combinedSlot].Type ) {
case MemoryCardType::MemoryCard_File:
return thisptr->impl.IsPSX( combinedSlot );
case MemoryCardType::MemoryCard_Folder:
return thisptr->implFolder.IsPSX( combinedSlot );
default:
return false;
}
}
static s32 PS2E_CALLBACK FileMcd_Read( PS2E_THISPTR thisptr, uint port, uint slot, u8 *dest, u32 adr, int size )
{
return thisptr->impl.Read( FileMcd_ConvertToSlot( port, slot ), dest, adr, size );
const uint combinedSlot = FileMcd_ConvertToSlot( port, slot );
switch ( g_Conf->Mcd[combinedSlot].Type ) {
case MemoryCardType::MemoryCard_File:
return thisptr->impl.Read( combinedSlot, dest, adr, size );
case MemoryCardType::MemoryCard_Folder:
return thisptr->implFolder.Read( combinedSlot, dest, adr, size );
default:
return 0;
}
}
static s32 PS2E_CALLBACK FileMcd_Save( PS2E_THISPTR thisptr, uint port, uint slot, const u8 *src, u32 adr, int size )
{
return thisptr->impl.Save( FileMcd_ConvertToSlot( port, slot ), src, adr, size );
const uint combinedSlot = FileMcd_ConvertToSlot( port, slot );
switch ( g_Conf->Mcd[combinedSlot].Type ) {
case MemoryCardType::MemoryCard_File:
return thisptr->impl.Save( combinedSlot, src, adr, size );
case MemoryCardType::MemoryCard_Folder:
return thisptr->implFolder.Save( combinedSlot, src, adr, size );
default:
return 0;
}
}
static s32 PS2E_CALLBACK FileMcd_EraseBlock( PS2E_THISPTR thisptr, uint port, uint slot, u32 adr )
{
return thisptr->impl.EraseBlock( FileMcd_ConvertToSlot( port, slot ), adr );
const uint combinedSlot = FileMcd_ConvertToSlot( port, slot );
switch ( g_Conf->Mcd[combinedSlot].Type ) {
case MemoryCardType::MemoryCard_File:
return thisptr->impl.EraseBlock( combinedSlot, adr );
case MemoryCardType::MemoryCard_Folder:
return thisptr->implFolder.EraseBlock( combinedSlot, adr );
default:
return 0;
}
}
static u64 PS2E_CALLBACK FileMcd_GetCRC( PS2E_THISPTR thisptr, uint port, uint slot )
{
return thisptr->impl.GetCRC( FileMcd_ConvertToSlot( port, slot ) );
const uint combinedSlot = FileMcd_ConvertToSlot( port, slot );
switch ( g_Conf->Mcd[combinedSlot].Type ) {
case MemoryCardType::MemoryCard_File:
return thisptr->impl.GetCRC( combinedSlot );
case MemoryCardType::MemoryCard_Folder:
return thisptr->implFolder.GetCRC( combinedSlot );
default:
return 0;
}
}
static void PS2E_CALLBACK FileMcd_NextFrame( PS2E_THISPTR thisptr, uint port, uint slot ) {
const uint combinedSlot = FileMcd_ConvertToSlot( port, slot );
switch ( g_Conf->Mcd[combinedSlot].Type ) {
//case MemoryCardType::MemoryCard_File:
// thisptr->impl.NextFrame( combinedSlot );
// break;
case MemoryCardType::MemoryCard_Folder:
thisptr->implFolder.NextFrame( combinedSlot );
break;
default:
return;
}
}
static void PS2E_CALLBACK FileMcd_ReIndex( PS2E_THISPTR thisptr, uint port, uint slot, const wxString& filter ) {
const uint combinedSlot = FileMcd_ConvertToSlot( port, slot );
switch ( g_Conf->Mcd[combinedSlot].Type ) {
//case MemoryCardType::MemoryCard_File:
// thisptr->impl.ReIndex( combinedSlot, filter );
// break;
case MemoryCardType::MemoryCard_Folder:
thisptr->implFolder.ReIndex( combinedSlot, g_Conf->EmuOptions.McdFolderAutoManage, filter );
break;
default:
return;
}
}
Component_FileMcd::Component_FileMcd()
@ -475,6 +574,8 @@ Component_FileMcd::Component_FileMcd()
api.McdSave = FileMcd_Save;
api.McdEraseBlock = FileMcd_EraseBlock;
api.McdGetCRC = FileMcd_GetCRC;
api.McdNextFrame = FileMcd_NextFrame;
api.McdReIndex = FileMcd_ReIndex;
}
@ -547,32 +648,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)
@ -597,6 +672,11 @@ bool isValidNewFilename( wxString filenameStringToTest, wxDirName atBasePath, wx
out_errorMessage = _("File name already exists");
return false;
}
if ( wxDirExists( (atBasePath + wxFileName(filenameStringToTest)).GetFullPath() ))
{
out_errorMessage = _( "File name already exists" );
return false;
}
wxFile fp;
if( !fp.Create( (atBasePath + wxFileName(filenameStringToTest)).GetFullPath() ))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,522 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2015 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <wx/file.h>
#include <wx/dir.h>
#include <wx/ffile.h>
#include <map>
#include <vector>
#include "PluginCallbacks.h"
#include "AppConfig.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;
static MemoryCardFileEntryDateTime FromWxDateTime( const wxDateTime& time ) {
MemoryCardFileEntryDateTime t;
if ( time.IsValid() ) {
wxDateTime::Tm tm = time.GetTm( wxDateTime::GMT9 );
t.unused = 0;
t.second = tm.sec;
t.minute = tm.min;
t.hour = tm.hour;
t.day = tm.mday;
t.month = tm.mon + 1;
t.year = tm.year;
} else {
t.unused = 0;
t.second = 0;
t.minute = 0;
t.hour = 0;
t.day = 0;
t.month = 0;
t.year = 0;
}
return t;
}
bool operator==( const MemoryCardFileEntryDateTime& other ) const {
return unused == other.unused && second == other.second && minute == other.minute && hour == other.hour
&& day == other.day && month == other.month && year == other.year;
}
bool operator!=( const MemoryCardFileEntryDateTime& other ) const {
return !( *this == other );
}
};
#pragma pack(pop)
// --------------------------------------------------------------------------------------
// MemoryCardFileEntry
// --------------------------------------------------------------------------------------
// Structure for directory and file relationships as stored on memory cards
#pragma pack(push, 1)
struct MemoryCardFileEntry {
enum MemoryCardFileModeFlags {
Mode_Read = 0x0001,
Mode_Write = 0x0002,
Mode_Execute = 0x0004,
Mode_CopyProtected = 0x0008,
Mode_File = 0x0010,
Mode_Directory = 0x0020,
Mode_Unknown0x0040 = 0x0040,
Mode_Unknown0x0080 = 0x0080,
Mode_Unknown0x0100 = 0x0100,
Mode_Unknown0x0200 = 0x0200,
Mode_Unknown0x0400 = 0x0400, // Maybe Mode_PS2_Save or something along those lines?
Mode_PocketStation = 0x0800,
Mode_PSX = 0x1000,
Mode_Unknown0x2000 = 0x2000, // Supposedly Mode_Hidden but files still show up in the PS2 browser with this set
Mode_Unknown0x4000 = 0x4000,
Mode_Used = 0x8000
};
union {
struct MemoryCardFileEntryData {
u32 mode;
u32 length; // number of bytes for file, number of files for dir
MemoryCardFileEntryDateTime timeCreated;
u32 cluster; // cluster the start of referred file or folder can be found in
u32 dirEntry; // parent directory entry number, only used if "." entry of subdir
MemoryCardFileEntryDateTime timeModified;
u32 attr;
u8 padding[0x1C];
u8 name[0x20];
u8 unused[0x1A0];
} data;
u8 raw[0x200];
} entry;
bool IsFile() const { return !!( entry.data.mode & Mode_File ); }
bool IsDir() const { return !!( entry.data.mode & Mode_Directory ); }
bool IsUsed() const { return !!( entry.data.mode & Mode_Used ); }
bool IsValid() const { return entry.data.mode != 0xFFFFFFFF; }
// checks if we're either "." or ".."
bool IsDotDir() const { return entry.data.name[0] == '.' && ( entry.data.name[1] == '\0' || ( entry.data.name[1] == '.' && entry.data.name[2] == '\0' ) ); }
static const u32 DefaultDirMode = Mode_Read | Mode_Write | Mode_Execute | Mode_Directory | Mode_Unknown0x0400 | Mode_Used;
static const u32 DefaultFileMode = Mode_Read | Mode_Write | Mode_Execute | Mode_File | Mode_Unknown0x0080 | Mode_Unknown0x0400 | Mode_Used;
};
#pragma pack(pop)
#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)
struct MemoryCardFileEntryTreeNode {
MemoryCardFileEntry entry;
std::vector<MemoryCardFileEntryTreeNode> subdir;
MemoryCardFileEntryTreeNode( const MemoryCardFileEntry& entry ) : entry(entry) {}
};
// --------------------------------------------------------------------------------------
// MemoryCardFileMetadataReference
// --------------------------------------------------------------------------------------
// Helper structure to quickly access file entries from any file data FAT cluster
struct MemoryCardFileMetadataReference {
MemoryCardFileMetadataReference* parent;
MemoryCardFileEntry* entry;
u32 consecutiveCluster;
// returns true if filename was modified and metadata containing the actual filename should be written
bool GetPath( wxFileName* fileName ) const;
};
// --------------------------------------------------------------------------------------
// FileAccessHelper
// --------------------------------------------------------------------------------------
// Small helper class to keep memory card files opened between calls to Read()/Save()
class FileAccessHelper {
protected:
wxFFile* m_file;
const MemoryCardFileEntry* m_entry;
wxString m_mode;
public:
FileAccessHelper();
~FileAccessHelper();
// Get an already opened file if possible, or open a new one and remember it
wxFFile* ReOpen( const wxFileName& folderName, MemoryCardFileMetadataReference* fileRef, const wxString& mode, bool writeMetadata = false );
// Close an open file, if any
void Close();
// removes characters from a PS2 file name that would be illegal in a Windows file system
// returns true if any changes were made
static bool CleanMemcardFilename( char* name );
protected:
// Open a new file and remember it for later
wxFFile* Open( const wxFileName& folderName, MemoryCardFileMetadataReference* fileRef, const wxString& mode, bool writeMetadata = false );
};
// --------------------------------------------------------------------------------------
// FolderMemoryCard
// --------------------------------------------------------------------------------------
// Fakes a memory card using a regular folder/file structure in the host file system
class FolderMemoryCard {
public:
// a few constants so we could in theory change the memory card size without too much effort
static const int IndirectFatClusterCount = 1; // should be 32 but only 1 is ever used
static const int PageSize = MemoryCardPage::PageSize;
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 TotalSizeRaw = TotalPages * PageSizeRaw;
static const u32 IndirectFatUnused = 0xFFFFFFFFu;
static const u32 LastDataCluster = 0x7FFFFFFFu;
static const u32 NextDataClusterMask = 0x7FFFFFFFu;
static const u32 DataClusterInUseMask = 0x80000000u;
static const int FramesAfterWriteUntilFlush = 60;
protected:
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];
union backupBlock2Union {
u32 programmedBlock;
u8 raw[BlockSize];
} m_backupBlock2;
// stores directory and file metadata
std::map<u32, MemoryCardFileEntryCluster> m_fileEntryDict;
// quick-access map of related file entry metadata for each memory card FAT cluster that contains file data
std::map<u32, MemoryCardFileMetadataReference> m_fileMetadataQuickAccess;
// holds a copy of modified pages of the memory card before they're flushed to the file system
std::map<u32, MemoryCardPage> m_cache;
// contains the state of how the data looked before the first write to it
// used to reduce the amount of disk I/O by not re-writing unchanged data that just happened to be
// touched in memory due to how actual physical memory cards have to erase and rewrite in blocks
std::map<u32, MemoryCardPage> m_oldDataCache;
// if > 0, the amount of frames until data is flushed to the file system
// reset to FramesAfterWriteUntilFlush on each write
int m_framesUntilFlush;
// used to figure out if contents were changed for savestate-related purposes, see GetCRC()
u64 m_timeLastWritten;
// remembers and keeps the last accessed file open for further access
FileAccessHelper m_lastAccessedFile;
// path to the folder that contains the files of this memory card
wxFileName m_folderName;
// PS2 memory card slot this card is inserted into
uint m_slot;
bool m_isEnabled;
// if set to false, nothing is actually written to the file system while flushing, and data is discarded instead
bool m_performFileWrites;
public:
FolderMemoryCard();
virtual ~FolderMemoryCard() throw() {}
void Lock();
void Unlock();
// Initialize & Load Memory Card with values configured in the Memory Card Manager
void Open( const bool enableFiltering, const wxString& filter );
// Initialize & Load Memory Card with provided custom values
void Open( const wxString& fullPath, const AppConfig::McdOptions& mcdOptions, const u32 sizeInClusters, const bool enableFiltering, const wxString& filter, bool simulateFileWrites = false );
// Close the memory card and flush changes to the file system. Set flush to false to not store changes.
void Close( bool flush = true );
s32 IsPresent() const;
void GetSizeInfo( PS2E_McdSizeInfo& outways ) const;
bool IsPSX() const;
s32 Read( u8 *dest, u32 adr, int size );
s32 Save( const u8 *src, u32 adr, int size );
s32 EraseBlock( u32 adr );
u64 GetCRC() const;
void SetSlot( uint slot );
u32 GetSizeInClusters() const;
// WARNING: The intended use-case for this is resetting back to 8MB if a differently-sized superblock was loaded
// setting to a different size is untested and will probably not work correctly
void SetSizeInClusters( u32 clusters );
// see SetSizeInClusters()
void SetSizeInMB( u32 megaBytes );
// called once per frame, used for flushing data after FramesAfterWriteUntilFlush frames of no writes
void NextFrame();
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() const;
// returns the in-memory address of data the given memory card adr corresponds to
// returns nullptr if adr corresponds to a folder or file entry
u8* GetSystemBlockPointer( const u32 adr );
// returns in-memory address of file or directory metadata searchCluster corresponds to
// returns nullptr if searchCluster contains something else
// - 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 searchCluster, const u32 entryNumber, const u32 offset );
// used by GetFileEntryPointer to find the correct cluster
// returns nullptr if searchCluster is not a file or directory metadata cluster
// - currentCluster: the cluster we're currently traversing
// - searchCluster: the cluster we want
// - fileCount: the number of files left in the directory currently traversed
MemoryCardFileEntryCluster* GetFileEntryCluster( const u32 currentCluster, const u32 searchCluster, const u32 fileCount );
// returns file entry of the file at the given searchCluster
// the passed fileName will be filled with a path to the file being accessed
// 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
// - sizeInClusters: total memory card size in clusters, 0 for default
// - enableFiltering: if set to true, only folders whose name contain the filter string are loaded
// - filter: can include multiple filters by separating them with "/"
void LoadMemoryCardData( const u32 sizeInClusters, const bool enableFiltering, const wxString& filter );
// 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() const;
// returns the total amount of data clusters available on the memory card, both used and unused
u32 GetAmountDataClusters() const;
// returns the lowest unused data cluster, relative to alloc_offset in the superblock
// returns 0xFFFFFFFFu when the memory card is full
u32 GetFreeDataCluster() const;
// returns the amount of unused data clusters
u32 GetAmountFreeDataClusters() const;
// returns the final cluster of the file or directory which is (partially) stored in the given cluster
u32 GetLastClusterOfData( const u32 cluster ) const;
// creates and returns a new file entry in the given directory entry, ready to be filled
// returns nullptr when the memory card is full
MemoryCardFileEntry* AppendFileEntryToDir( const MemoryCardFileEntry* const dirEntry );
// adds a folder in the host file system to the memory card, including all files and subdirectories
// - dirEntry: the entry of the directory in the parent directory, or the root "." entry
// - dirPath: the full path to the directory in the host file system
// - parent: pointer to the parent dir's quick-access reference element
// - enableFiltering and filter: filter loaded contents, see LoadMemoryCardData()
bool AddFolder( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, MemoryCardFileMetadataReference* parent = nullptr, const bool enableFiltering = false, const wxString& filter = L"" );
// adds a file in the host file sytem to the memory card
// - dirEntry: the entry of the directory in the parent directory, or the root "." entry
// - dirPath: the full path to the directory containing the file in the host file system
// - fileName: the name of the file, without path
// - parent: pointer to the parent dir's quick-access reference element
bool AddFile( MemoryCardFileEntry* const dirEntry, const wxString& dirPath, const wxString& fileName, MemoryCardFileMetadataReference* parent = nullptr );
// calculates the amount of clusters a directory would use up if put into a memory card
u32 CalculateRequiredClustersOfDirectory( const wxString& dirPath ) const;
// adds a file to the quick-access dictionary, so it can be accessed more efficiently (ie, without searching through the entire file system) later
void AddFileEntryToMetadataQuickAccess( MemoryCardFileEntry* const entry, MemoryCardFileMetadataReference* const parent );
// creates a reference to a directory entry, so it can be passed as parent to other files/directories
MemoryCardFileMetadataReference* AddDirEntryToMetadataQuickAccess( MemoryCardFileEntry* const entry, MemoryCardFileMetadataReference* const parent );
// read data from the memory card, ignoring the cache
// do NOT attempt to read ECC with this method, it will not work
void ReadDataWithoutCache( u8* const dest, const u32 adr, const u32 dataLength );
bool ReadFromFile( u8 *dest, u32 adr, u32 dataLength );
bool WriteToFile( const u8* src, u32 adr, u32 dataLength );
// 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
bool FlushPage( const u32 page );
// flush a memory card cluster of the cache to the internal data and/or host file system
bool FlushCluster( const u32 cluster );
// flush a whole memory card block of the cache to the internal data and/or host file system
bool FlushBlock( const u32 block );
// flush the superblock to the internal data and/or host file system
void FlushSuperBlock();
// flush all directory and file entries to the internal data
void FlushFileEntries();
// flush a directory's file entries and all its subdirectories to the internal data
void FlushFileEntries( const u32 dirCluster, const u32 remainingFiles, const wxString& dirPath = L"", MemoryCardFileMetadataReference* parent = nullptr );
// "delete" (prepend '_pcsx2_deleted_' to) any files that exist in oldFileEntries but no longer exist in m_fileEntryDict
// also calls RemoveUnchangedDataFromCache() since both operate on comparing with the old file entires
void FlushDeletedFilesAndRemoveUnchangedDataFromCache( const std::vector<MemoryCardFileEntryTreeNode>& oldFileEntries );
// recursive worker method of the above
// - newCluster: Current directory dotdir cluster of the new entries.
// - newFileCount: Number of file entries in the new directory.
// - dirPath: Path to the current directory relative to the root of the memcard. Must be identical for both entries.
void FlushDeletedFilesAndRemoveUnchangedDataFromCache( const std::vector<MemoryCardFileEntryTreeNode>& oldFileEntries, const u32 newCluster, const u32 newFileCount, const wxString& dirPath );
// try and remove unchanged data from m_cache
// oldEntry and newEntry should be equivalent entries found by FindEquivalent()
void RemoveUnchangedDataFromCache( const MemoryCardFileEntry* const oldEntry, const MemoryCardFileEntry* const newEntry );
// write data as Save() normally would, but ignore the cache; used for flushing
s32 WriteWithoutCache( const u8 *src, u32 adr, int size );
// copies the contents of m_fileEntryDict into the tree structure fileEntryTree
void CopyEntryDictIntoTree( std::vector<MemoryCardFileEntryTreeNode>* fileEntryTree, const u32 cluster, const u32 fileCount );
// find equivalent (same name and type) of searchEntry in m_fileEntryDict in the directory indicated by cluster
const MemoryCardFileEntry* FindEquivalent( const MemoryCardFileEntry* searchEntry, const u32 cluster, const u32 fileCount );
void SetTimeLastReadToNow();
void SetTimeLastWrittenToNow();
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];
// stores the specifics of the current filtering settings, so they can be
// re-applied automatically when memory cards are reloaded
bool m_enableFiltering = true;
wxString m_lastKnownFilter = L"";
public:
FolderMemoryCardAggregator();
virtual ~FolderMemoryCardAggregator() throw( ) {}
void Open();
void Close();
void SetFiltering( const bool enableFiltering );
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 );
void ReIndex( uint slot, const bool enableFiltering, const wxString& filter );
};

View File

@ -30,6 +30,9 @@
#include <wx/dir.h>
bool CopyDirectory( const wxString& from, const wxString& to );
bool RemoveDirectory( const wxString& dirname );
using namespace pxSizerFlags;
using namespace Panels;
@ -54,19 +57,48 @@ bool EnumerateMemoryCard( McdSlotItem& dest, const wxFileName& filename, const w
{
dest.IsFormatted = false;
dest.IsPresent = false;
dest.IsPSX = false;
dest.Type = MemoryCardType::MemoryCard_None;
const wxString fullpath( filename.GetFullPath() );
if( !filename.FileExists() ) return false;
//DevCon.WriteLn( fullpath );
wxFFile mcdFile( fullpath );
if( !mcdFile.IsOpened() ) return false; // wx should log the error for us.
if ( filename.FileExists() ) {
// might be a memory card file
wxFFile mcdFile( fullpath );
if ( !mcdFile.IsOpened() ) { return false; } // wx should log the error for us.
wxFileOffset length = mcdFile.Length();
wxFileOffset length = mcdFile.Length();
if( length < (1024*528) && length != 0x20000 )
{
Console.Warning( "... MemoryCard appears to be truncated. Ignoring." );
if( length < (1024*528) && length != 0x20000 )
{
Console.Warning( "... MemoryCard appears to be truncated. Ignoring." );
return false;
}
dest.SizeInMB = (uint)( length / ( 1024 * 528 * 2 ) );
if ( length == 0x20000 ) {
dest.IsPSX = true; // PSX memcard;
dest.SizeInMB = 1; // MegaBIT
}
dest.Type = MemoryCardType::MemoryCard_File;
dest.IsFormatted = IsMcdFormatted( mcdFile );
filename.GetTimes( NULL, &dest.DateModified, &dest.DateCreated );
} else if ( filename.DirExists() ) {
// might be a memory card folder
wxFileName superBlockFileName( fullpath, L"_pcsx2_superblock" );
if ( !superBlockFileName.FileExists() ) { return false; }
wxFFile mcdFile( superBlockFileName.GetFullPath() );
if ( !mcdFile.IsOpened() ) { return false; }
dest.SizeInMB = 0;
dest.Type = MemoryCardType::MemoryCard_Folder;
dest.IsFormatted = IsMcdFormatted( mcdFile );
superBlockFileName.GetTimes( NULL, &dest.DateModified, &dest.DateCreated );
} else {
// is neither
return false;
}
@ -75,17 +107,6 @@ bool EnumerateMemoryCard( McdSlotItem& dest, const wxFileName& filename, const w
if( filename.GetFullPath() == (basePath+filename.GetFullName()).GetFullPath() )
dest.Filename = filename.GetFullName();
dest.SizeInMB = (uint)(length / (1024 * 528 * 2));
if(length == 0x20000)
{
dest.IsPSX = true; // PSX memcard;
dest.SizeInMB = 1; // MegaBIT
}
dest.IsFormatted = IsMcdFormatted( mcdFile );
filename.GetTimes( NULL, &dest.DateModified, &dest.DateCreated );
return true;
}
@ -448,6 +469,7 @@ enum McdMenuId
McdMenuId_RefreshList,
McdMenuId_AssignUnassign,
McdMenuId_Duplicate,
McdMenuId_Convert,
};
@ -474,6 +496,7 @@ Panels::MemoryCardListPanel_Simple::MemoryCardListPanel_Simple( wxWindow* parent
m_button_Duplicate = new wxButton(this, wxID_ANY, _("Duplicate ..."));
m_button_Rename = new wxButton(this, wxID_ANY, _("Rename ..."));
m_button_Create = new wxButton(this, wxID_ANY, _("Create ..."));
m_button_Convert = new wxButton(this, wxID_ANY, _("Convert ..."));
// ------------------------------------
// Sizer / Layout Section
@ -493,6 +516,8 @@ Panels::MemoryCardListPanel_Simple::MemoryCardListPanel_Simple( wxWindow* parent
*s_leftside_buttons += m_button_Rename;
*s_leftside_buttons += 2;
*s_leftside_buttons += m_button_Create;
*s_leftside_buttons += 2;
*s_leftside_buttons += m_button_Convert;
SetSizerAndFit(GetSizer());
parent->SetWindowStyle(parent->GetWindowStyle() | wxRESIZE_BORDER);
@ -508,12 +533,14 @@ Panels::MemoryCardListPanel_Simple::MemoryCardListPanel_Simple( wxWindow* parent
// Connect( m_button_Mount->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnMountCard));
Connect( m_button_Create->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnCreateOrDeleteCard));
Connect( m_button_Convert->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnConvertCard));
Connect( m_button_Rename->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnRenameFile));
Connect( m_button_Duplicate->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnDuplicateFile));
Connect( m_button_AssignUnassign->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnAssignUnassignFile));
// Popup Menu Connections!
Connect( McdMenuId_Create, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnCreateOrDeleteCard) );
Connect( McdMenuId_Convert, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnConvertCard) );
//Connect( McdMenuId_Mount, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnMountCard) );
Connect( McdMenuId_Rename, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnRenameFile) );
Connect( McdMenuId_AssignUnassign, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnAssignUnassignFile) );
@ -544,6 +571,7 @@ void Panels::MemoryCardListPanel_Simple::UpdateUI()
m_button_Rename->Disable();
m_button_Duplicate->Disable();
m_button_AssignUnassign->Disable();
m_button_Convert->Disable();
return;
}
@ -563,6 +591,8 @@ void Panels::MemoryCardListPanel_Simple::UpdateUI()
wxString dupTip = _("Create a duplicate of this memory card ...");
pxSetToolTip( m_button_Duplicate, dupTip );
m_button_Convert->Enable( card.IsPresent && card.IsFormatted && !card.IsPSX );
//m_button_Create->Enable( card.Slot>=0 || card.IsPresent);
m_button_Create->SetLabel( card.IsPresent ? _("Delete") : _("Create ...") );
@ -595,6 +625,7 @@ void Panels::MemoryCardListPanel_Simple::Apply()
Console.WriteLn( L"Apply Memory cards:" );
for( uint slot=0; slot<8; ++slot )
{
g_Conf->Mcd[slot].Type = m_Cards[slot].Type;
g_Conf->Mcd[slot].Enabled = m_Cards[slot].IsEnabled && m_Cards[slot].IsPresent;
if (m_Cards[slot].IsPresent)
g_Conf->Mcd[slot].Filename = m_Cards[slot].Filename;
@ -623,7 +654,7 @@ void Panels::MemoryCardListPanel_Simple::AppStatusEvent_OnSettingsApplied()
//automatically create the enabled but non-existing file such that it can be managed (else will get created anyway on boot)
wxString targetFile = (GetMcdPath() + m_Cards[slot].Filename.GetFullName()).GetFullPath();
if ( m_Cards[slot].IsEnabled && !wxFileExists( targetFile ) )
if ( m_Cards[slot].IsEnabled && !( wxFileExists( targetFile ) || wxDirExists( targetFile ) ) )
{
wxString errMsg;
if (isValidNewFilename(m_Cards[slot].Filename.GetFullName(), GetMcdPath(), errMsg, 5))
@ -639,7 +670,7 @@ void Panels::MemoryCardListPanel_Simple::AppStatusEvent_OnSettingsApplied()
}
}
if ( !m_Cards[slot].IsEnabled || !wxFileExists( targetFile ) )
if ( !m_Cards[slot].IsEnabled || !( wxFileExists( targetFile ) || wxDirExists( targetFile ) ) )
{
m_Cards[slot].IsEnabled = false;
m_Cards[slot].IsPresent = false;
@ -728,6 +759,28 @@ void Panels::MemoryCardListPanel_Simple::UiCreateNewCard( McdSlotItem& card )
closed_core.AllowResume();
}
void Panels::MemoryCardListPanel_Simple::UiConvertCard( McdSlotItem& card ) {
if ( !card.IsPresent ) {
Console.WriteLn( "Error: Aborted: Convert mcd invoked but but a file is not associated." );
return;
}
ScopedCoreThreadClose closed_core;
AppConfig::McdOptions config;
config.Filename = card.Filename.GetFullName();
config.Enabled = card.IsEnabled;
config.Type = card.Type;
Dialogs::ConvertMemoryCardDialog dialog( this, m_FolderPicker->GetPath(), config );
wxWindowID result = dialog.ShowModal();
if ( result != wxID_CANCEL ) {
Apply();
RefreshSelections();
}
closed_core.AllowResume();
}
void Panels::MemoryCardListPanel_Simple::UiDeleteCard( McdSlotItem& card )
{
@ -757,7 +810,12 @@ void Panels::MemoryCardListPanel_Simple::UiDeleteCard( McdSlotItem& card )
card.IsEnabled=false;
Apply();
wxRemoveFile( fullpath.GetFullPath() );
if ( fullpath.FileExists() ) {
wxRemoveFile( fullpath.GetFullPath() );
} else {
RemoveDirectory( fullpath.GetFullPath() );
}
RefreshSelections();
closed_core.AllowResume();
@ -817,7 +875,8 @@ bool Panels::MemoryCardListPanel_Simple::UiDuplicateCard(McdSlotItem& src, McdSl
ScopedBusyCursor doh( Cursor_ReallyBusy );
ScopedCoreThreadClose closed_core;
if( !wxCopyFile( srcfile.GetFullPath(), destfile.GetFullPath(), true ) )
if( !( ( srcfile.FileExists() && wxCopyFile( srcfile.GetFullPath(), destfile.GetFullPath(), true ) )
|| ( !srcfile.FileExists() && CopyDirectory( srcfile.GetFullPath(), destfile.GetFullPath() ) ) ) )
{
wxString heading;
heading.Printf( pxE( L"Failed: Destination memory card '%s' is in use." ),
@ -918,6 +977,18 @@ void Panels::MemoryCardListPanel_Simple::OnCreateOrDeleteCard(wxCommandEvent& ev
UiCreateNewCard( card );
}
void Panels::MemoryCardListPanel_Simple::OnConvertCard(wxCommandEvent& evt) {
int selectedViewIndex = m_listview->GetFirstSelected();
if ( wxNOT_FOUND == selectedViewIndex ) {
return;
}
McdSlotItem& card( GetCardForViewIndex( selectedViewIndex ) );
if ( card.IsPresent ) {
UiConvertCard( card );
}
}
//enable/disapbe port
/*
void Panels::MemoryCardListPanel_Simple::OnMountCard(wxCommandEvent& evt)
@ -1077,6 +1148,9 @@ void Panels::MemoryCardListPanel_Simple::OnOpenItemContextMenu(wxListEvent& evt)
junk->Append( McdMenuId_Duplicate, _("Duplicate card ...") );
junk->Append( McdMenuId_Rename, _("Rename card ...") );
junk->Append( McdMenuId_Create, _("Delete card") );
if (card.IsFormatted && !card.IsPSX) {
junk->Append( McdMenuId_Convert, _("Convert card") );
}
}
else
junk->Append( McdMenuId_Create, _("Create a new card ...") );
@ -1106,9 +1180,26 @@ void Panels::MemoryCardListPanel_Simple::ReadFilesAtMcdFolder(){
wxArrayString memcardList;
wxDir::GetAllFiles(m_FolderPicker->GetPath().ToString(), &memcardList, L"*.ps2", wxDIR_FILES);
wxDir::GetAllFiles(m_FolderPicker->GetPath().ToString(), &memcardList, L"*.mcd", wxDIR_FILES);
wxDir::GetAllFiles(m_FolderPicker->GetPath().ToString(), &memcardList, L"*.mcr", wxDIR_FILES);
wxString filename = m_FolderPicker->GetPath().ToString();
wxDir memcardDir( filename );
if ( memcardDir.IsOpened() ) {
// add memory card files
wxDir::GetAllFiles( filename, &memcardList, L"*.ps2", wxDIR_FILES );
wxDir::GetAllFiles( filename, &memcardList, L"*.mcd", wxDIR_FILES );
wxDir::GetAllFiles( filename, &memcardList, L"*.mcr", wxDIR_FILES );
// add memory card folders
wxString dirname;
if ( memcardDir.GetFirst( &dirname, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN ) ) {
do {
wxFileName superBlockFileName( wxFileName( filename, dirname ).GetFullPath(), L"_pcsx2_superblock" );
if ( superBlockFileName.FileExists() ) {
memcardList.Add( superBlockFileName.GetPath() );
}
} while ( memcardDir.GetNext( &dirname ) );
}
}
for(uint i = 0; i < memcardList.size(); i++) {
McdSlotItem currentCardFile;

View File

@ -156,7 +156,7 @@ wxString MemoryCardListView_Simple::OnGetItemText(long item, long column) const
return prefix + res;
}
*/
case McdColS_Size: return prefix + ( !it.IsPresent ? L"" : (it.IsPSX? pxsFmt( L"%u MBit", it.SizeInMB ) : pxsFmt( L"%u MiB", it.SizeInMB )) );
case McdColS_Size: return prefix + ( !it.IsPresent ? L"" : (it.IsPSX? pxsFmt( L"%u MBit", it.SizeInMB ) : ( it.SizeInMB > 0 ? pxsFmt( L"%u MiB", it.SizeInMB ) : L"Auto" ) ) );
case McdColS_Formatted: return prefix + ( !it.IsPresent ? L"" : ( it.IsFormatted ? _("Yes") : _("No")) );
case McdColS_Type: return prefix + ( !it.IsPresent ? L"" : ( it.IsPSX? _("PSX") : _("PS2")) );
case McdColS_DateModified: return prefix + ( !it.IsPresent ? L"" : it.DateModified.FormatDate() );

View File

@ -40,6 +40,7 @@ struct McdSlotItem
{
int Slot; //0-7: internal slot. -1: unrelated to an internal slot (the rest of the files at the folder).
bool IsPresent; //Whether or not a file is associated with this item (true/false when 0<=Slot<=7. Always true when Slot==-1)
MemoryCardType Type; //The implementation used for this memory card
//Only meaningful when IsPresent==true (a file exists for this item):
wxFileName Filename; // full pathname
@ -212,6 +213,8 @@ namespace Panels
// Doubles as Create and Delete buttons
wxButton* m_button_Create;
wxButton* m_button_Convert;
// Doubles as Mount and Unmount buttons ("Enable"/"Disable" port)
// wxButton* m_button_Mount;
@ -236,6 +239,7 @@ namespace Panels
protected:
void OnCreateOrDeleteCard(wxCommandEvent& evt);
void OnConvertCard(wxCommandEvent& evt);
void OnMountCard(wxCommandEvent& evt);
// void OnRelocateCard(wxCommandEvent& evt);
void OnRenameFile(wxCommandEvent& evt);
@ -269,6 +273,7 @@ namespace Panels
virtual void UiRenameCard( McdSlotItem& card );
virtual void UiCreateNewCard( McdSlotItem& card );
virtual void UiConvertCard( McdSlotItem& card );
virtual void UiDeleteCard( McdSlotItem& card );
virtual void UiAssignUnassignFile( McdSlotItem& card );
@ -284,6 +289,7 @@ namespace Panels
protected:
//pxCheckBox* m_check_Multitap[2];
pxCheckBox* m_check_Ejection;
pxCheckBox* m_folderAutoIndex;
//moved to the system menu, just below "Save State"
//pxCheckBox* m_check_SavestateBackup;

View File

@ -636,6 +636,7 @@
<ClCompile Include="..\..\gui\MainFrame.cpp" />
<ClCompile Include="..\..\gui\MainMenuClicks.cpp" />
<ClCompile Include="..\..\gui\MemoryCardFile.cpp" />
<ClCompile Include="..\..\gui\MemoryCardFolder.cpp" />
<ClCompile Include="..\..\gui\MessageBoxes.cpp" />
<ClCompile Include="..\..\gui\MSWstuff.cpp" />
<ClCompile Include="..\..\gui\pxLogTextCtrl.cpp" />
@ -645,6 +646,7 @@
<ClCompile Include="..\..\gui\Dialogs\AssertionDialog.cpp" />
<ClCompile Include="..\..\gui\Dialogs\BaseConfigurationDialog.cpp" />
<ClCompile Include="..\..\gui\Dialogs\ConfirmationDialogs.cpp" />
<ClCompile Include="..\..\gui\Dialogs\ConvertMemoryCardDialog.cpp" />
<ClCompile Include="..\..\gui\Dialogs\CreateMemoryCardDialog.cpp" />
<ClCompile Include="..\..\gui\Dialogs\FirstTimeWizard.cpp" />
<ClCompile Include="..\..\gui\Dialogs\ImportSettingsDialog.cpp" />

View File

@ -635,6 +635,9 @@
<ClCompile Include="..\..\gui\MemoryCardFile.cpp">
<Filter>AppHost</Filter>
</ClCompile>
<ClCompile Include="..\..\gui\MemoryCardFolder.cpp">
<Filter>AppHost</Filter>
</ClCompile>
<ClCompile Include="..\..\gui\MessageBoxes.cpp">
<Filter>AppHost</Filter>
</ClCompile>
@ -662,6 +665,9 @@
<ClCompile Include="..\..\gui\Dialogs\ConfirmationDialogs.cpp">
<Filter>AppHost\Dialogs</Filter>
</ClCompile>
<ClCompile Include="..\..\gui\Dialogs\ConvertMemoryCardDialog.cpp">
<Filter>AppHost\Dialogs</Filter>
</ClCompile>
<ClCompile Include="..\..\gui\Dialogs\CreateMemoryCardDialog.cpp">
<Filter>AppHost\Dialogs</Filter>
</ClCompile>

View File

@ -636,6 +636,7 @@
<ClCompile Include="..\..\gui\MainFrame.cpp" />
<ClCompile Include="..\..\gui\MainMenuClicks.cpp" />
<ClCompile Include="..\..\gui\MemoryCardFile.cpp" />
<ClCompile Include="..\..\gui\MemoryCardFolder.cpp" />
<ClCompile Include="..\..\gui\MessageBoxes.cpp" />
<ClCompile Include="..\..\gui\MSWstuff.cpp" />
<ClCompile Include="..\..\gui\pxLogTextCtrl.cpp" />
@ -645,6 +646,7 @@
<ClCompile Include="..\..\gui\Dialogs\AssertionDialog.cpp" />
<ClCompile Include="..\..\gui\Dialogs\BaseConfigurationDialog.cpp" />
<ClCompile Include="..\..\gui\Dialogs\ConfirmationDialogs.cpp" />
<ClCompile Include="..\..\gui\Dialogs\ConvertMemoryCardDialog.cpp" />
<ClCompile Include="..\..\gui\Dialogs\CreateMemoryCardDialog.cpp" />
<ClCompile Include="..\..\gui\Dialogs\FirstTimeWizard.cpp" />
<ClCompile Include="..\..\gui\Dialogs\ImportSettingsDialog.cpp" />

View File

@ -635,6 +635,9 @@
<ClCompile Include="..\..\gui\MemoryCardFile.cpp">
<Filter>AppHost</Filter>
</ClCompile>
<ClCompile Include="..\..\gui\MemoryCardFolder.cpp">
<Filter>AppHost</Filter>
</ClCompile>
<ClCompile Include="..\..\gui\MessageBoxes.cpp">
<Filter>AppHost</Filter>
</ClCompile>
@ -662,6 +665,9 @@
<ClCompile Include="..\..\gui\Dialogs\ConfirmationDialogs.cpp">
<Filter>AppHost\Dialogs</Filter>
</ClCompile>
<ClCompile Include="..\..\gui\Dialogs\ConvertMemoryCardDialog.cpp">
<Filter>AppHost\Dialogs</Filter>
</ClCompile>
<ClCompile Include="..\..\gui\Dialogs\CreateMemoryCardDialog.cpp">
<Filter>AppHost\Dialogs</Filter>
</ClCompile>