2009-09-13 17:11:35 +00:00
/* PCSX2 - PS2 Emulator for PCs
2010-05-03 14:08:02 +00:00
* Copyright (C) 2002-2010 PCSX2 Dev Team
2010-04-25 00:31:27 +00:00
*
2009-09-13 17:11:35 +00:00
* 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"
2022-01-15 05:43:15 +10:00
#include "common/FileSystem.h"
2021-09-01 16:31:46 -04:00
#include "common/SafeArray.inl"
2022-05-20 00:46:33 +10:00
#include "common/Path.h"
2021-09-22 17:38:58 +10:00
#include "common/StringUtil.h"
2010-05-23 15:20:52 +00:00
2022-01-15 05:43:15 +10:00
#include <array>
2016-09-18 20:44:10 +00:00
#include <chrono>
2014-11-10 15:39:40 +01:00
#include "MemoryCardFile.h"
#include "MemoryCardFolder.h"
2009-09-13 17:11:35 +00:00
#include "System.h"
2021-09-17 18:42:39 +10:00
#include "Config.h"
2021-12-19 00:43:50 +10:00
#include "Host.h"
2022-08-06 13:45:15 +10:00
#include "IconsFontAwesome5.h"
2009-09-13 17:11:35 +00:00
2013-06-28 10:43:50 +00:00
#include "svnrev.h"
2009-09-13 17:11:35 +00:00
2022-05-18 23:27:23 +10:00
#include "fmt/core.h"
2014-11-10 15:39:40 +01:00
#include <map>
2009-09-13 17:11:35 +00:00
2020-11-24 09:46:32 -03:00
static const int MCD_SIZE = 1024 * 8 * 16 ; // Legacy PSX card default size
2010-05-23 15:20:52 +00:00
2020-11-24 09:46:32 -03:00
static const int MC2_MBSIZE = 1024 * 528 * 2 ; // Size of a single megabyte of card data
2009-09-13 17:11:35 +00:00
2022-01-15 05:43:15 +10:00
static const char * s_folder_mem_card_id_file = "_pcsx2_superblock" ;
2021-06-28 05:18:44 +02:00
bool FileMcd_Open = false ;
2019-06-17 23:28:57 -03:00
// ECC code ported from mymc
// https://sourceforge.net/p/mymc-opl/code/ci/master/tree/ps2mc_ecc.py
// Public domain license
2020-11-24 09:46:32 -03:00
static u32 CalculateECC ( u8 * buf )
2019-06-17 23:28:57 -03:00
{
2020-11-24 11:08:37 -03:00
const u8 parity_table [ 256 ] = { 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 0 , 1 , 1 ,
0 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 0 ,
1 , 1 , 0 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 1 ,
0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 0 ,
1 , 1 , 0 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 0 , 1 , 1 ,
0 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 0 ,
1 , 1 , 0 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 0 , 1 , 1 , 0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 ,
1 , 0 , 1 , 1 , 0 };
2019-06-17 23:28:57 -03:00
2020-11-24 11:08:37 -03:00
const u8 column_parity_mask [ 256 ] = { 0 , 7 , 22 , 17 , 37 , 34 , 51 , 52 , 52 , 51 , 34 , 37 , 17 , 22 ,
7 , 0 , 67 , 68 , 85 , 82 , 102 , 97 , 112 , 119 , 119 , 112 , 97 , 102 , 82 , 85 , 68 , 67 , 82 , 85 , 68 , 67 , 119 , 112 ,
97 , 102 , 102 , 97 , 112 , 119 , 67 , 68 , 85 , 82 , 17 , 22 , 7 , 0 , 52 , 51 , 34 , 37 , 37 , 34 , 51 , 52 , 0 , 7 , 22 , 17 ,
97 , 102 , 119 , 112 , 68 , 67 , 82 , 85 , 85 , 82 , 67 , 68 , 112 , 119 , 102 , 97 , 34 , 37 , 52 , 51 , 7 , 0 , 17 , 22 ,
22 , 17 , 0 , 7 , 51 , 52 , 37 , 34 , 51 , 52 , 37 , 34 , 22 , 17 , 0 , 7 , 7 , 0 , 17 , 22 , 34 , 37 , 52 , 51 , 112 , 119 , 102 ,
97 , 85 , 82 , 67 , 68 , 68 , 67 , 82 , 85 , 97 , 102 , 119 , 112 , 112 , 119 , 102 , 97 , 85 , 82 , 67 , 68 , 68 , 67 , 82 ,
85 , 97 , 102 , 119 , 112 , 51 , 52 , 37 , 34 , 22 , 17 , 0 , 7 , 7 , 0 , 17 , 22 , 34 , 37 , 52 , 51 , 34 , 37 , 52 , 51 , 7 , 0 ,
17 , 22 , 22 , 17 , 0 , 7 , 51 , 52 , 37 , 34 , 97 , 102 , 119 , 112 , 68 , 67 , 82 , 85 , 85 , 82 , 67 , 68 , 112 , 119 , 102 ,
97 , 17 , 22 , 7 , 0 , 52 , 51 , 34 , 37 , 37 , 34 , 51 , 52 , 0 , 7 , 22 , 17 , 82 , 85 , 68 , 67 , 119 , 112 , 97 , 102 , 102 ,
97 , 112 , 119 , 67 , 68 , 85 , 82 , 67 , 68 , 85 , 82 , 102 , 97 , 112 , 119 , 119 , 112 , 97 , 102 , 82 , 85 , 68 , 67 ,
0 , 7 , 22 , 17 , 37 , 34 , 51 , 52 , 52 , 51 , 34 , 37 , 17 , 22 , 7 , 0 };
2019-06-17 23:28:57 -03:00
u8 column_parity = 0x77 ;
u8 line_parity_0 = 0x7F ;
u8 line_parity_1 = 0x7F ;
2020-11-24 09:46:32 -03:00
for ( int i = 0 ; i < 128 ; i ++ )
{
2019-06-17 23:28:57 -03:00
u8 b = buf [ i ];
column_parity ^= column_parity_mask [ b ];
2020-11-24 09:46:32 -03:00
if ( parity_table [ b ])
{
2019-06-17 23:28:57 -03:00
line_parity_0 ^= ~ i ;
line_parity_1 ^= i ;
}
}
return column_parity | ( line_parity_0 << 8 ) | ( line_parity_1 << 16 );
}
2022-05-20 00:46:33 +10:00
static bool ConvertNoECCtoRAW ( const char * file_in , const char * file_out )
2019-06-17 23:28:57 -03:00
{
2022-05-20 00:46:33 +10:00
auto fin = FileSystem :: OpenManagedCFile ( file_in , "rb" );
if ( ! fin )
return false ;
2019-06-17 23:28:57 -03:00
2022-05-20 00:46:33 +10:00
auto fout = FileSystem :: OpenManagedCFile ( file_out , "wb" );
if ( ! fout )
return false ;
const s64 size = FileSystem :: FSize64 ( fin . get ());
u8 buffer [ 512 ];
for ( s64 i = 0 ; i < ( size / 512 ); i ++ )
2020-11-24 09:46:32 -03:00
{
2022-05-20 00:46:33 +10:00
if ( std :: fread ( buffer , sizeof ( buffer ), 1 , fin . get ()) != 1 ||
std :: fwrite ( buffer , sizeof ( buffer ), 1 , fout . get ()) != 1 )
2020-11-24 09:46:32 -03:00
{
2022-05-20 00:46:33 +10:00
return false ;
2019-06-17 23:28:57 -03:00
}
2022-05-20 00:46:33 +10:00
for ( int j = 0 ; j < 4 ; j ++ )
{
u32 checksum = CalculateECC ( & buffer [ j * 128 ]);
if ( std :: fwrite ( & checksum , 3 , 1 , fout . get ()) != 1 )
return false ;
}
u32 nullbytes = 0 ;
if ( std :: fwrite ( & nullbytes , sizeof ( nullbytes ), 1 , fout . get ()) != 1 )
return false ;
2019-06-17 23:28:57 -03:00
}
2022-05-20 00:46:33 +10:00
if ( std :: fflush ( fout . get ()) != 0 )
return false ;
return true ;
2019-06-17 23:28:57 -03:00
}
2022-05-20 00:46:33 +10:00
static bool ConvertRAWtoNoECC ( const char * file_in , const char * file_out )
2019-06-17 23:28:57 -03:00
{
2022-05-20 00:46:33 +10:00
auto fin = FileSystem :: OpenManagedCFile ( file_in , "rb" );
if ( ! fin )
return false ;
2019-06-17 23:28:57 -03:00
2022-05-20 00:46:33 +10:00
auto fout = FileSystem :: OpenManagedCFile ( file_out , "wb" );
if ( ! fout )
return false ;
const s64 size = FileSystem :: FSize64 ( fin . get ());
u8 buffer [ 512 ];
u8 checksum [ 16 ];
for ( s64 i = 0 ; i < ( size / 528 ); i ++ )
2020-11-24 09:46:32 -03:00
{
2022-05-20 00:46:33 +10:00
if ( std :: fread ( buffer , sizeof ( buffer ), 1 , fin . get ()) != 1 ||
std :: fwrite ( buffer , sizeof ( buffer ), 1 , fout . get ()) != 1 ||
std :: fread ( checksum , sizeof ( checksum ), 1 , fin . get ()) != 1 )
2020-11-24 09:46:32 -03:00
{
2022-05-20 00:46:33 +10:00
return false ;
2019-06-17 23:28:57 -03:00
}
}
2022-05-20 00:46:33 +10:00
if ( std :: fflush ( fout . get ()) != 0 )
return false ;
return true ;
2019-06-17 23:28:57 -03:00
}
2009-09-13 17:11:35 +00:00
// --------------------------------------------------------------------------------------
2010-04-25 00:31:27 +00:00
// FileMemoryCard
2009-09-13 17:11:35 +00:00
// --------------------------------------------------------------------------------------
// Provides thread-safe direct file IO mapping.
//
class FileMemoryCard
{
protected :
2022-05-20 00:46:33 +10:00
std :: FILE * m_file [ 8 ];
std :: string m_filenames [ 8 ];
2020-11-24 09:46:32 -03:00
u8 m_effeffs [ 528 * 16 ];
SafeArray < u8 > m_currentdata ;
u64 m_chksum [ 8 ];
bool m_ispsx [ 8 ];
u32 m_chkaddr ;
2009-09-13 17:11:35 +00:00
public :
FileMemoryCard ();
2017-05-06 13:01:33 +02:00
virtual ~ FileMemoryCard () = default ;
2009-09-13 17:11:35 +00:00
void Lock ();
void Unlock ();
2010-05-26 01:57:08 +00:00
void Open ();
void Close ();
2020-11-24 09:46:32 -03:00
s32 IsPresent ( uint slot );
2021-05-11 15:40:24 +02:00
void GetSizeInfo ( uint slot , McdSizeInfo & outways );
2020-11-24 09:46:32 -03:00
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 );
2016-03-28 17:21:10 +02:00
2009-09-13 17:11:35 +00:00
protected :
2022-05-20 00:46:33 +10:00
bool Seek ( std :: FILE * f , u32 adr );
bool Create ( const char * mcdFile , uint sizeInMB );
2009-09-13 17:11:35 +00:00
};
2010-05-23 15:20:52 +00:00
uint FileMcd_GetMtapPort ( uint slot )
{
2020-11-24 09:46:32 -03:00
switch ( slot )
2010-05-23 15:20:52 +00:00
{
2020-11-24 09:46:32 -03:00
case 0 :
case 2 :
case 3 :
case 4 :
return 0 ;
case 1 :
case 5 :
case 6 :
case 7 :
return 1 ;
2010-05-23 15:20:52 +00:00
2020-11-24 09:46:32 -03:00
jNO_DEFAULT
2010-05-23 15:20:52 +00:00
}
2020-11-24 09:46:32 -03:00
return 0 ; // technically unreachable.
2010-05-23 15:20:52 +00:00
}
2010-05-28 16:54:42 +00:00
// Returns the multitap slot number, range 1 to 3 (slot 0 refers to the standard
// 1st and 2nd player slots).
2010-05-23 15:20:52 +00:00
uint FileMcd_GetMtapSlot ( uint slot )
{
2020-11-24 09:46:32 -03:00
switch ( slot )
2010-05-23 15:20:52 +00:00
{
2020-11-24 09:46:32 -03:00
case 0 :
case 1 :
pxFailDev ( "Invalid parameter in call to GetMtapSlot -- specified slot is one of the base slots, not a Multitap slot." );
break ;
2010-05-23 15:20:52 +00:00
2020-11-24 09:46:32 -03:00
case 2 :
case 3 :
case 4 :
return slot - 1 ;
case 5 :
case 6 :
case 7 :
return slot - 4 ;
2010-05-23 15:20:52 +00:00
2020-11-24 09:46:32 -03:00
jNO_DEFAULT
2010-05-23 15:20:52 +00:00
}
2020-11-24 09:46:32 -03:00
return 0 ; // technically unreachable.
2010-05-23 15:20:52 +00:00
}
2020-11-24 09:46:32 -03:00
bool FileMcd_IsMultitapSlot ( uint slot )
2010-05-23 15:20:52 +00:00
{
return ( slot > 1 );
}
2023-01-06 14:58:23 +00:00
2021-09-22 17:38:58 +10:00
std :: string FileMcd_GetDefaultName ( uint slot )
2010-05-23 15:20:52 +00:00
{
2020-11-24 09:46:32 -03:00
if ( FileMcd_IsMultitapSlot ( slot ))
2021-09-22 17:38:58 +10:00
return StringUtil :: StdStringFromFormat ( "Mcd-Multitap%u-Slot%02u.ps2" , FileMcd_GetMtapPort ( slot ) + 1 , FileMcd_GetMtapSlot ( slot ) + 1 );
2010-05-23 15:20:52 +00:00
else
2021-09-22 17:38:58 +10:00
return StringUtil :: StdStringFromFormat ( "Mcd%03u.ps2" , slot + 1 );
2010-05-23 15:20:52 +00:00
}
2009-09-13 17:11:35 +00:00
FileMemoryCard :: FileMemoryCard ()
2022-08-24 03:44:51 +02:00
: m_chkaddr ( 0 )
2009-09-13 17:11:35 +00:00
{
2020-11-24 09:46:32 -03:00
memset8 < 0xff > ( m_effeffs );
2010-05-26 01:57:08 +00:00
}
2009-09-13 17:11:35 +00:00
2010-05-26 01:57:08 +00:00
void FileMemoryCard :: Open ()
{
2020-11-24 09:46:32 -03:00
for ( int slot = 0 ; slot < 8 ; ++ slot )
2009-09-13 17:11:35 +00:00
{
2022-05-20 00:46:33 +10:00
m_filenames [ slot ] = {};
2020-11-24 09:46:32 -03:00
if ( FileMcd_IsMultitapSlot ( slot ))
2010-05-26 01:57:08 +00:00
{
2020-11-24 09:46:32 -03:00
if ( ! EmuConfig . MultitapPort0_Enabled && ( FileMcd_GetMtapPort ( slot ) == 0 ))
continue ;
if ( ! EmuConfig . MultitapPort1_Enabled && ( FileMcd_GetMtapPort ( slot ) == 1 ))
continue ;
2010-05-26 01:57:08 +00:00
}
2010-05-23 15:20:52 +00:00
2022-05-20 00:46:33 +10:00
std :: string fname ( EmuConfig . FullpathToMcd ( slot ));
std :: string_view str ( fname );
2010-05-26 01:57:08 +00:00
bool cont = false ;
2022-05-20 00:46:33 +10:00
if ( fname . empty ())
2010-05-26 01:57:08 +00:00
{
2022-05-20 00:46:33 +10:00
str = "[empty filename]" ;
2010-05-26 01:57:08 +00:00
cont = true ;
}
2021-09-17 18:42:39 +10:00
if ( ! EmuConfig . Mcd [ slot ]. Enabled )
2010-05-26 01:57:08 +00:00
{
2022-05-20 00:46:33 +10:00
str = "[disabled]" ;
2010-05-26 01:57:08 +00:00
cont = true ;
}
2021-10-02 19:47:10 +10:00
if ( EmuConfig . Mcd [ slot ]. Type != MemoryCardType :: File )
2020-11-24 09:46:32 -03:00
{
2022-05-20 00:46:33 +10:00
str = "[is not memcard file]" ;
2015-05-09 03:22:17 +02:00
cont = true ;
}
2022-05-20 00:46:33 +10:00
Console . WriteLn ( cont ? Color_Gray : Color_Green , "McdSlot %u [File]: %.*s" , slot ,
static_cast < int > ( str . size ()), str . data ());
2020-11-24 09:46:32 -03:00
if ( cont )
continue ;
2010-05-23 15:20:52 +00:00
2022-05-20 00:46:33 +10:00
if ( FileSystem :: GetPathFileSize ( fname . c_str ()) <= 0 )
2009-09-13 17:11:35 +00:00
{
2010-05-26 01:57:08 +00:00
// FIXME : Ideally this should prompt the user for the size of the
2010-05-30 13:24:44 +00:00
// memory card file they would like to create, instead of trying to
2010-05-26 01:57:08 +00:00
// create one automatically.
2020-11-24 09:46:32 -03:00
2022-05-20 00:46:33 +10:00
if ( ! Create ( fname . c_str (), 8 ))
2009-09-13 17:11:35 +00:00
{
2022-05-20 00:46:33 +10:00
Host :: ReportFormattedErrorAsync ( "Memory Card" , "Could not create a memory card: \n\n %s \n\n " ,
fname . c_str ());
2009-09-13 17:11:35 +00:00
}
}
2010-05-23 15:20:52 +00:00
// [TODO] : Add memcard size detection and report it to the console log.
2010-05-26 01:57:08 +00:00
// (8MB, 256Mb, formatted, unformatted, etc ...)
2010-05-23 15:20:52 +00:00
2022-04-12 22:16:05 +10:00
#ifdef _WIN32
2022-05-20 00:46:33 +10:00
FileSystem :: SetPathCompression ( fname . c_str (), EmuConfig . McdCompressNTFS );
2010-05-23 15:20:52 +00:00
#endif
2022-05-20 00:46:33 +10:00
if ( StringUtil :: EndsWith ( fname , ".bin" ))
2019-06-17 23:28:57 -03:00
{
2022-05-20 00:46:33 +10:00
std :: string newname ( fname + "x" );
if ( ! ConvertNoECCtoRAW ( fname . c_str (), newname . c_str ()))
2019-06-17 23:28:57 -03:00
{
2022-05-20 00:46:33 +10:00
Console . Error ( "Could convert memory card: %s" , fname . c_str ());
FileSystem :: DeleteFilePath ( newname . c_str ());
2019-06-17 23:28:57 -03:00
continue ;
}
2022-05-20 00:46:33 +10:00
// store the original filename
2022-06-06 22:47:09 +10:00
m_file [ slot ] = FileSystem :: OpenSharedCFile ( newname . c_str (), "r+b" , FileSystem :: FileShareMode :: DenyWrite );
2022-05-20 00:46:33 +10:00
}
else
{
2022-06-06 22:47:09 +10:00
m_file [ slot ] = FileSystem :: OpenSharedCFile ( fname . c_str (), "r+b" , FileSystem :: FileShareMode :: DenyWrite );
2019-06-17 23:28:57 -03:00
}
2022-05-20 00:46:33 +10:00
if ( ! m_file [ slot ])
2010-05-23 15:20:52 +00:00
{
// Translation note: detailed description should mention that the memory card will be disabled
// for the duration of this session.
2022-05-20 00:46:33 +10:00
Host :: ReportFormattedErrorAsync ( "Memory Card" , "Access denied to memory card: \n\n %s \n\n "
2022-11-21 18:35:34 -05:00
"Another instance of PCSX2 may be using this memory card. Close any other instances of PCSX2, or restart your computer.%s" ,
fname . c_str (),
#ifdef WIN32
" \n\n If your memory card is in a write-protected folder such as \" Program Files \" or \" Program Files (x86) \" , move it to another folder, such as \" Documents \" or \" Desktop \" ."
#else
""
#endif
);
2010-05-23 15:20:52 +00:00
}
2013-07-19 13:43:33 +00:00
else // Load checksum
{
2022-05-20 00:46:33 +10:00
m_filenames [ slot ] = std :: move ( fname );
m_ispsx [ slot ] = FileSystem :: FSize64 ( m_file [ slot ]) == 0x20000 ;
2013-07-19 13:43:33 +00:00
m_chkaddr = 0x210 ;
2022-05-20 00:46:33 +10:00
if ( ! m_ispsx [ slot ] && FileSystem :: FSeek64 ( m_file [ slot ], m_chkaddr , SEEK_SET ) == 0 )
2022-11-28 15:49:37 +01:00
{
const size_t read_result = std :: fread ( & m_chksum [ slot ], sizeof ( m_chksum [ slot ]), 1 , m_file [ slot ]);
if ( read_result == 0 )
Host :: ReportFormattedErrorAsync ( "Memory Card" , "Error reading memcard. \n " );
}
2013-07-19 13:43:33 +00:00
}
2009-09-13 17:11:35 +00:00
}
}
2010-05-26 01:57:08 +00:00
void FileMemoryCard :: Close ()
{
2020-11-24 09:46:32 -03:00
for ( int slot = 0 ; slot < 8 ; ++ slot )
2013-07-19 13:43:33 +00:00
{
2022-05-20 00:46:33 +10:00
if ( ! m_file [ slot ])
continue ;
// Store checksum
if ( ! m_ispsx [ slot ] && FileSystem :: FSeek64 ( m_file [ slot ], m_chkaddr , SEEK_SET ) == 0 )
std :: fwrite ( & m_chksum [ slot ], sizeof ( m_chksum [ slot ]), 1 , m_file [ slot ]);
std :: fclose ( m_file [ slot ]);
m_file [ slot ] = nullptr ;
if ( StringUtil :: EndsWith ( m_filenames [ slot ], ".bin" ))
2020-11-24 09:46:32 -03:00
{
2022-05-20 00:46:33 +10:00
const std :: string name_in ( m_filenames [ slot ] + 'x' );
if ( ConvertRAWtoNoECC ( name_in . c_str (), m_filenames [ slot ]. c_str ()))
FileSystem :: DeleteFilePath ( name_in . c_str ());
2014-06-29 08:48:32 +02:00
}
2022-05-20 00:46:33 +10:00
m_filenames [ slot ] = {};
2013-07-19 13:43:33 +00:00
}
2010-05-26 01:57:08 +00:00
}
2009-09-13 17:11:35 +00:00
// Returns FALSE if the seek failed (is outside the bounds of the file).
2022-05-20 00:46:33 +10:00
bool FileMemoryCard :: Seek ( std :: FILE * f , u32 adr )
2009-09-13 17:11:35 +00:00
{
2022-05-20 00:46:33 +10:00
const s64 size = FileSystem :: FSize64 ( f );
2009-09-13 17:11:35 +00:00
// If anyone knows why this filesize logic is here (it appears to be related to legacy PSX
2010-04-25 00:31:27 +00:00
// cards, perhaps hacked support for some special emulator-specific memcard formats that
2010-01-31 02:21:58 +00:00
// had header info?), then please replace this comment with something useful. Thanks! -- air
2009-09-13 17:11:35 +00:00
u32 offset = 0 ;
2010-04-25 00:31:27 +00:00
2020-11-24 09:46:32 -03:00
if ( size == MCD_SIZE + 64 )
2009-09-13 17:11:35 +00:00
offset = 64 ;
2020-11-24 09:46:32 -03:00
else if ( size == MCD_SIZE + 3904 )
2009-09-13 17:11:35 +00:00
offset = 3904 ;
else
{
// perform sanity checks here?
}
2022-05-20 00:46:33 +10:00
return ( FileSystem :: FSeek64 ( f , adr + offset , SEEK_SET ) == 0 );
2009-09-13 17:11:35 +00:00
}
// returns FALSE if an error occurred (either permission denied or disk full)
2022-05-20 00:46:33 +10:00
bool FileMemoryCard :: Create ( const char * mcdFile , uint sizeInMB )
2009-09-13 17:11:35 +00:00
{
//int enc[16] = {0x77,0x7f,0x7f,0x77,0x7f,0x7f,0x77,0x7f,0x7f,0x77,0x7f,0x7f,0,0,0,0};
2022-05-20 00:46:33 +10:00
Console . WriteLn ( "(FileMcd) Creating new %uMB memory card: %s" , sizeInMB , mcdFile );
2010-05-23 15:20:52 +00:00
2022-05-20 00:46:33 +10:00
auto fp = FileSystem :: OpenManagedCFile ( mcdFile , "wb" );
if ( ! fp )
2020-11-24 09:46:32 -03:00
return false ;
2009-09-13 17:11:35 +00:00
2020-11-24 09:46:32 -03:00
for ( uint i = 0 ; i < ( MC2_MBSIZE * sizeInMB ) / sizeof ( m_effeffs ); i ++ )
2009-09-13 17:11:35 +00:00
{
2022-05-20 00:46:33 +10:00
if ( std :: fwrite ( m_effeffs , sizeof ( m_effeffs ), 1 , fp . get ()) != 1 )
2009-09-13 17:11:35 +00:00
return false ;
}
return true ;
}
2020-11-24 09:46:32 -03:00
s32 FileMemoryCard :: IsPresent ( uint slot )
2009-09-13 17:11:35 +00:00
{
2022-05-20 00:46:33 +10:00
return m_file [ slot ] != nullptr ;
2009-09-13 17:11:35 +00:00
}
2021-05-11 15:40:24 +02:00
void FileMemoryCard :: GetSizeInfo ( uint slot , McdSizeInfo & outways )
2010-05-26 19:45:33 +00:00
{
2020-11-24 09:46:32 -03:00
outways . SectorSize = 512 ; // 0x0200
outways . EraseBlockSizeInSectors = 16 ; // 0x0010
outways . Xor = 18 ; // 0x12, XOR 02 00 00 10
2010-05-26 19:45:33 +00:00
2022-05-20 00:46:33 +10:00
if ( pxAssert ( m_file [ slot ]))
outways . McdSizeInSectors = static_cast < u32 > ( FileSystem :: FSize64 ( m_file [ slot ])) / ( outways . SectorSize + outways . EraseBlockSizeInSectors );
2010-05-26 19:45:33 +00:00
else
2020-11-24 09:46:32 -03:00
outways . McdSizeInSectors = 0x4000 ;
2013-07-19 13:43:33 +00:00
2020-11-24 09:46:32 -03:00
u8 * pdata = ( u8 * ) & outways . McdSizeInSectors ;
2013-07-19 13:43:33 +00:00
outways . Xor ^= pdata [ 0 ] ^ pdata [ 1 ] ^ pdata [ 2 ] ^ pdata [ 3 ];
2010-05-26 19:45:33 +00:00
}
2020-11-24 09:46:32 -03:00
bool FileMemoryCard :: IsPSX ( uint slot )
2013-04-27 16:24:02 +00:00
{
2013-07-19 13:43:33 +00:00
return m_ispsx [ slot ];
2013-04-27 16:24:02 +00:00
}
2020-11-24 09:46:32 -03:00
s32 FileMemoryCard :: Read ( uint slot , u8 * dest , u32 adr , int size )
2009-09-13 17:11:35 +00:00
{
2022-05-20 00:46:33 +10:00
std :: FILE * mcfp = m_file [ slot ];
if ( ! mcfp )
2009-09-13 17:11:35 +00:00
{
2020-11-24 09:46:32 -03:00
DevCon . Error ( "(FileMcd) Ignoring attempted read from disabled slot." );
2009-09-13 17:11:35 +00:00
memset ( dest , 0 , size );
return 1 ;
}
2020-11-24 09:46:32 -03:00
if ( ! Seek ( mcfp , adr ))
return 0 ;
2022-05-20 00:46:33 +10:00
return std :: fread ( dest , size , 1 , mcfp ) == 1 ;
2009-09-13 17:11:35 +00:00
}
2020-11-24 09:46:32 -03:00
s32 FileMemoryCard :: Save ( uint slot , const u8 * src , u32 adr , int size )
2009-09-13 17:11:35 +00:00
{
2022-05-20 00:46:33 +10:00
std :: FILE * mcfp = m_file [ slot ];
2009-09-13 17:11:35 +00:00
2022-05-20 00:46:33 +10:00
if ( ! mcfp )
2009-09-13 17:11:35 +00:00
{
2020-11-24 09:46:32 -03:00
DevCon . Error ( "(FileMcd) Ignoring attempted save/write to disabled slot." );
2009-09-13 17:11:35 +00:00
return 1 ;
}
2020-11-24 09:46:32 -03:00
if ( m_ispsx [ slot ])
2009-09-13 17:11:35 +00:00
{
2020-11-24 09:46:32 -03:00
m_currentdata . MakeRoomFor ( size );
for ( int i = 0 ; i < size ; i ++ )
m_currentdata [ i ] = src [ i ];
2013-07-19 13:43:33 +00:00
}
else
{
2020-11-24 09:46:32 -03:00
if ( ! Seek ( mcfp , adr ))
return 0 ;
m_currentdata . MakeRoomFor ( size );
2013-07-19 13:43:33 +00:00
2022-11-28 15:49:37 +01:00
const size_t read_result = std :: fread ( m_currentdata . GetPtr (), size , 1 , mcfp );
if ( read_result == 0 )
Host :: ReportFormattedErrorAsync ( "Memory Card" , "Error reading memcard. \n " );
2020-11-24 09:46:32 -03:00
for ( int i = 0 ; i < size ; i ++ )
2013-07-19 13:43:33 +00:00
{
if (( m_currentdata [ i ] & src [ i ]) != src [ i ])
Console . Warning ( "(FileMcd) Warning: writing to uncleared data. (%d) [%08X]" , slot , adr );
m_currentdata [ i ] &= src [ i ];
}
// Checksumness
{
2020-11-24 09:46:32 -03:00
if ( adr == m_chkaddr )
2013-07-19 13:43:33 +00:00
Console . Warning ( "(FileMcd) Warning: checksum sector overwritten. (%d)" , slot );
2020-11-24 09:46:32 -03:00
u64 * pdata = ( u64 * ) & m_currentdata [ 0 ];
2013-07-19 13:43:33 +00:00
u32 loops = size / 8 ;
2020-11-24 09:46:32 -03:00
for ( u32 i = 0 ; i < loops ; i ++ )
2013-07-19 13:43:33 +00:00
m_chksum [ slot ] ^= pdata [ i ];
}
2009-09-13 17:11:35 +00:00
}
2020-11-24 09:46:32 -03:00
if ( ! Seek ( mcfp , adr ))
return 0 ;
2016-09-18 20:44:10 +00:00
2022-05-20 00:46:33 +10:00
if ( std :: fwrite ( m_currentdata . GetPtr (), size , 1 , mcfp ) == 1 )
2020-11-24 09:46:32 -03:00
{
2016-09-18 20:44:10 +00:00
static auto last = std :: chrono :: time_point < std :: chrono :: system_clock > ();
std :: chrono :: duration < float > elapsed = std :: chrono :: system_clock :: now () - last ;
2020-11-24 09:46:32 -03:00
if ( elapsed > std :: chrono :: seconds ( 5 ))
{
2022-08-06 13:45:15 +10:00
Host :: AddIconOSDMessage ( fmt :: format ( "MemoryCardSave{}" , slot ), ICON_FA_SD_CARD ,
2022-11-23 00:49:45 +10:00
fmt :: format ( "Memory card '{}' was saved to storage." , Path :: GetFileName ( m_filenames [ slot ])),
Host :: OSD_INFO_DURATION );
2016-09-18 20:44:10 +00:00
last = std :: chrono :: system_clock :: now ();
}
return 1 ;
}
return 0 ;
2009-09-13 17:11:35 +00:00
}
2020-11-24 09:46:32 -03:00
s32 FileMemoryCard :: EraseBlock ( uint slot , u32 adr )
2009-09-13 17:11:35 +00:00
{
2022-05-20 00:46:33 +10:00
std :: FILE * mcfp = m_file [ slot ];
if ( ! mcfp )
2009-09-13 17:11:35 +00:00
{
2020-11-24 09:46:32 -03:00
DevCon . Error ( "MemoryCard: Ignoring erase for disabled slot." );
2009-09-13 17:11:35 +00:00
return 1 ;
}
2020-11-24 09:46:32 -03:00
if ( ! Seek ( mcfp , adr ))
return 0 ;
2022-05-20 00:46:33 +10:00
return std :: fwrite ( m_effeffs , sizeof ( m_effeffs ), 1 , mcfp ) == 1 ;
2009-09-13 17:11:35 +00:00
}
2020-11-24 09:46:32 -03:00
u64 FileMemoryCard :: GetCRC ( uint slot )
2009-09-13 17:11:35 +00:00
{
2022-05-20 00:46:33 +10:00
std :: FILE * mcfp = m_file [ slot ];
if ( ! mcfp )
2020-11-24 09:46:32 -03:00
return 0 ;
2009-09-13 17:11:35 +00:00
u64 retval = 0 ;
2013-07-19 13:43:33 +00:00
2020-11-24 09:46:32 -03:00
if ( m_ispsx [ slot ])
2009-09-13 17:11:35 +00:00
{
2020-11-24 09:46:32 -03:00
if ( ! Seek ( mcfp , 0 ))
return 0 ;
2013-07-19 13:43:33 +00:00
2022-05-20 00:46:33 +10:00
const s64 mcfpsize = FileSystem :: FSize64 ( mcfp );
if ( mcfpsize < 0 )
return 0 ;
2013-07-19 13:43:33 +00:00
// Process the file in 4k chunks. Speeds things up significantly.
2020-11-24 09:46:32 -03:00
u64 buffer [ 528 * 8 ]; // use 528 (sector size), ensures even divisibility
2022-05-20 00:46:33 +10:00
const uint filesize = static_cast < uint > ( mcfpsize ) / sizeof ( buffer );
2020-11-24 09:46:32 -03:00
for ( uint i = filesize ; i ; -- i )
2013-07-19 13:43:33 +00:00
{
2022-05-20 00:46:33 +10:00
if ( std :: fread ( buffer , sizeof ( buffer ), 1 , mcfp ) != 1 )
return 0 ;
2021-08-30 19:43:40 -05:00
for ( uint t = 0 ; t < std :: size ( buffer ); ++ t )
2013-07-19 13:43:33 +00:00
retval ^= buffer [ t ];
}
}
else
{
retval = m_chksum [ slot ];
2009-09-13 17:11:35 +00:00
}
return retval ;
}
// --------------------------------------------------------------------------------------
// MemoryCard Component API Bindings
// --------------------------------------------------------------------------------------
2021-05-12 14:22:10 +02:00
namespace Mcd
2009-09-13 17:11:35 +00:00
{
2021-05-11 15:40:24 +02:00
FileMemoryCard impl ; // class-based implementations we refer to when API is invoked
2015-05-09 03:22:17 +02:00
FolderMemoryCardAggregator implFolder ;
2021-05-12 14:22:10 +02:00
}; // namespace Mcd
2009-09-13 17:11:35 +00:00
2020-11-24 09:46:32 -03:00
uint FileMcd_ConvertToSlot ( uint port , uint slot )
2010-05-23 15:20:52 +00:00
{
2020-11-24 09:46:32 -03:00
if ( slot == 0 )
return port ;
if ( port == 0 )
return slot + 1 ; // multitap 1
return slot + 4 ; // multitap 2
2010-05-23 15:20:52 +00:00
}
2021-05-12 13:46:03 +02:00
void FileMcd_EmuOpen ()
2010-05-26 01:57:08 +00:00
{
2021-06-28 05:18:44 +02:00
if ( FileMcd_Open )
return ;
FileMcd_Open = true ;
2015-07-31 01:06:31 +02:00
// detect inserted memory card types
2020-11-24 09:46:32 -03:00
for ( uint slot = 0 ; slot < 8 ; ++ slot )
{
2022-06-06 22:47:53 +10:00
if ( EmuConfig . Mcd [ slot ]. Filename . empty ())
{
EmuConfig . Mcd [ slot ]. Type = MemoryCardType :: Empty ;
}
else if ( EmuConfig . Mcd [ slot ]. Enabled )
2020-11-24 09:46:32 -03:00
{
2021-10-02 19:47:10 +10:00
MemoryCardType type = MemoryCardType :: File ; // default to file if we can't find anything at the path so it gets auto-generated
2015-07-31 01:06:31 +02:00
2022-05-20 00:46:33 +10:00
const std :: string path ( EmuConfig . FullpathToMcd ( slot ));
if ( FileSystem :: DirectoryExists ( path . c_str ()))
2021-10-02 19:47:10 +10:00
type = MemoryCardType :: Folder ;
2015-07-31 01:06:31 +02:00
2021-09-17 18:42:39 +10:00
EmuConfig . Mcd [ slot ]. Type = type ;
2015-07-31 01:06:31 +02:00
}
}
2021-05-12 14:22:10 +02:00
Mcd :: impl . Open ();
2021-09-17 18:42:39 +10:00
Mcd :: implFolder . SetFiltering ( EmuConfig . McdFolderAutoManage );
2021-05-12 14:22:10 +02:00
Mcd :: implFolder . Open ();
2010-05-26 01:57:08 +00:00
}
2021-05-12 13:46:03 +02:00
void FileMcd_EmuClose ()
2010-05-26 01:57:08 +00:00
{
2021-06-28 05:18:44 +02:00
if ( ! FileMcd_Open )
return ;
FileMcd_Open = false ;
2021-05-12 14:22:10 +02:00
Mcd :: implFolder . Close ();
Mcd :: impl . Close ();
2010-05-26 01:57:08 +00:00
}
2021-05-12 13:46:03 +02:00
s32 FileMcd_IsPresent ( uint port , uint slot )
2009-09-13 17:11:35 +00:00
{
2020-11-24 09:46:32 -03:00
const uint combinedSlot = FileMcd_ConvertToSlot ( port , slot );
2021-09-17 18:42:39 +10:00
switch ( EmuConfig . Mcd [ combinedSlot ]. Type )
2020-11-24 09:46:32 -03:00
{
2021-10-02 19:47:10 +10:00
case MemoryCardType :: File :
2021-05-12 14:22:10 +02:00
return Mcd :: impl . IsPresent ( combinedSlot );
2021-10-02 19:47:10 +10:00
case MemoryCardType :: Folder :
2021-05-12 14:22:10 +02:00
return Mcd :: implFolder . IsPresent ( combinedSlot );
2020-11-24 09:46:32 -03:00
default :
return false ;
2015-05-09 03:22:17 +02:00
}
2009-09-13 17:11:35 +00:00
}
2021-05-12 13:46:03 +02:00
void FileMcd_GetSizeInfo ( uint port , uint slot , McdSizeInfo * outways )
2010-05-26 19:45:33 +00:00
{
2020-11-24 09:46:32 -03:00
const uint combinedSlot = FileMcd_ConvertToSlot ( port , slot );
2021-09-17 18:42:39 +10:00
switch ( EmuConfig . Mcd [ combinedSlot ]. Type )
2020-11-24 09:46:32 -03:00
{
2021-10-02 19:47:10 +10:00
case MemoryCardType :: File :
2021-05-12 14:22:10 +02:00
Mcd :: impl . GetSizeInfo ( combinedSlot , * outways );
2020-11-24 09:46:32 -03:00
break ;
2021-10-02 19:47:10 +10:00
case MemoryCardType :: Folder :
2021-05-12 14:22:10 +02:00
Mcd :: implFolder . GetSizeInfo ( combinedSlot , * outways );
2020-11-24 09:46:32 -03:00
break ;
default :
return ;
2015-05-09 03:22:17 +02:00
}
2010-05-26 19:45:33 +00:00
}
2021-05-12 13:46:03 +02:00
bool FileMcd_IsPSX ( uint port , uint slot )
2013-04-27 16:24:02 +00:00
{
2020-11-24 09:46:32 -03:00
const uint combinedSlot = FileMcd_ConvertToSlot ( port , slot );
2021-09-17 18:42:39 +10:00
switch ( EmuConfig . Mcd [ combinedSlot ]. Type )
2020-11-24 09:46:32 -03:00
{
2021-10-02 19:47:10 +10:00
case MemoryCardType :: File :
2021-05-12 14:22:10 +02:00
return Mcd :: impl . IsPSX ( combinedSlot );
2021-10-02 19:47:10 +10:00
case MemoryCardType :: Folder :
2021-05-12 14:22:10 +02:00
return Mcd :: implFolder . IsPSX ( combinedSlot );
2020-11-24 09:46:32 -03:00
default :
return false ;
2015-05-09 03:22:17 +02:00
}
2013-04-27 16:24:02 +00:00
}
2021-05-12 13:46:03 +02:00
s32 FileMcd_Read ( uint port , uint slot , u8 * dest , u32 adr , int size )
2009-09-13 17:11:35 +00:00
{
2020-11-24 09:46:32 -03:00
const uint combinedSlot = FileMcd_ConvertToSlot ( port , slot );
2021-09-17 18:42:39 +10:00
switch ( EmuConfig . Mcd [ combinedSlot ]. Type )
2020-11-24 09:46:32 -03:00
{
2021-10-02 19:47:10 +10:00
case MemoryCardType :: File :
2021-05-12 14:22:10 +02:00
return Mcd :: impl . Read ( combinedSlot , dest , adr , size );
2021-10-02 19:47:10 +10:00
case MemoryCardType :: Folder :
2021-05-12 14:22:10 +02:00
return Mcd :: implFolder . Read ( combinedSlot , dest , adr , size );
2020-11-24 09:46:32 -03:00
default :
return 0 ;
2015-05-09 03:22:17 +02:00
}
2009-09-13 17:11:35 +00:00
}
2021-05-12 13:46:03 +02:00
s32 FileMcd_Save ( uint port , uint slot , const u8 * src , u32 adr , int size )
2009-09-13 17:11:35 +00:00
{
2020-11-24 09:46:32 -03:00
const uint combinedSlot = FileMcd_ConvertToSlot ( port , slot );
2021-09-17 18:42:39 +10:00
switch ( EmuConfig . Mcd [ combinedSlot ]. Type )
2020-11-24 09:46:32 -03:00
{
2021-10-02 19:47:10 +10:00
case MemoryCardType :: File :
2021-05-12 14:22:10 +02:00
return Mcd :: impl . Save ( combinedSlot , src , adr , size );
2021-10-02 19:47:10 +10:00
case MemoryCardType :: Folder :
2021-05-12 14:22:10 +02:00
return Mcd :: implFolder . Save ( combinedSlot , src , adr , size );
2020-11-24 09:46:32 -03:00
default :
return 0 ;
2015-05-09 03:22:17 +02:00
}
2009-09-13 17:11:35 +00:00
}
2021-05-12 13:46:03 +02:00
s32 FileMcd_EraseBlock ( uint port , uint slot , u32 adr )
2009-09-13 17:11:35 +00:00
{
2020-11-24 09:46:32 -03:00
const uint combinedSlot = FileMcd_ConvertToSlot ( port , slot );
2021-09-17 18:42:39 +10:00
switch ( EmuConfig . Mcd [ combinedSlot ]. Type )
2020-11-24 09:46:32 -03:00
{
2021-10-02 19:47:10 +10:00
case MemoryCardType :: File :
2021-05-12 14:22:10 +02:00
return Mcd :: impl . EraseBlock ( combinedSlot , adr );
2021-10-02 19:47:10 +10:00
case MemoryCardType :: Folder :
2021-05-12 14:22:10 +02:00
return Mcd :: implFolder . EraseBlock ( combinedSlot , adr );
2020-11-24 09:46:32 -03:00
default :
return 0 ;
2015-05-09 03:22:17 +02:00
}
2009-09-13 17:11:35 +00:00
}
2021-05-12 13:46:03 +02:00
u64 FileMcd_GetCRC ( uint port , uint slot )
2009-09-13 17:11:35 +00:00
{
2020-11-24 09:46:32 -03:00
const uint combinedSlot = FileMcd_ConvertToSlot ( port , slot );
2021-09-17 18:42:39 +10:00
switch ( EmuConfig . Mcd [ combinedSlot ]. Type )
2020-11-24 09:46:32 -03:00
{
2021-10-02 19:47:10 +10:00
case MemoryCardType :: File :
2021-05-12 14:22:10 +02:00
return Mcd :: impl . GetCRC ( combinedSlot );
2021-10-02 19:47:10 +10:00
case MemoryCardType :: Folder :
2021-05-12 14:22:10 +02:00
return Mcd :: implFolder . GetCRC ( combinedSlot );
2020-11-24 09:46:32 -03:00
default :
return 0 ;
2015-05-09 03:22:17 +02:00
}
2009-09-13 17:11:35 +00:00
}
2021-05-12 13:46:03 +02:00
void FileMcd_NextFrame ( uint port , uint slot )
2020-11-24 09:46:32 -03:00
{
const uint combinedSlot = FileMcd_ConvertToSlot ( port , slot );
2021-09-17 18:42:39 +10:00
switch ( EmuConfig . Mcd [ combinedSlot ]. Type )
2020-11-24 09:46:32 -03:00
{
//case MemoryCardType::MemoryCard_File:
2021-05-12 14:22:10 +02:00
// Mcd::impl.NextFrame( combinedSlot );
2020-11-24 09:46:32 -03:00
// break;
2021-10-02 19:47:10 +10:00
case MemoryCardType :: Folder :
2021-05-12 14:22:10 +02:00
Mcd :: implFolder . NextFrame ( combinedSlot );
2020-11-24 09:46:32 -03:00
break ;
default :
return ;
2015-05-09 03:22:17 +02:00
}
2014-11-10 15:39:40 +01:00
}
2022-05-20 00:46:33 +10:00
bool FileMcd_ReIndex ( uint port , uint slot , const std :: string & filter )
2020-11-24 09:46:32 -03:00
{
const uint combinedSlot = FileMcd_ConvertToSlot ( port , slot );
2021-09-17 18:42:39 +10:00
switch ( EmuConfig . Mcd [ combinedSlot ]. Type )
2020-11-24 09:46:32 -03:00
{
2021-10-02 19:47:10 +10:00
//case MemoryCardType::File:
2021-05-12 14:22:10 +02:00
// return Mcd::impl.ReIndex( combinedSlot, filter );
2020-11-24 09:46:32 -03:00
// break;
2021-10-02 19:47:10 +10:00
case MemoryCardType :: Folder :
2021-09-17 18:42:39 +10:00
return Mcd :: implFolder . ReIndex ( combinedSlot , EmuConfig . McdFolderAutoManage , filter );
2020-11-24 09:46:32 -03:00
break ;
default :
return false ;
2015-05-09 20:28:40 +02:00
}
}
2009-09-13 17:11:35 +00:00
// --------------------------------------------------------------------------------------
// Library API Implementations
// --------------------------------------------------------------------------------------
2022-01-15 05:43:15 +10:00
static MemoryCardFileType GetMemoryCardFileTypeFromSize ( s64 size )
{
if ( size == ( 8 * MC2_MBSIZE ))
return MemoryCardFileType :: PS2_8MB ;
else if ( size == ( 16 * MC2_MBSIZE ))
return MemoryCardFileType :: PS2_16MB ;
else if ( size == ( 32 * MC2_MBSIZE ))
return MemoryCardFileType :: PS2_32MB ;
else if ( size == ( 64 * MC2_MBSIZE ))
return MemoryCardFileType :: PS2_64MB ;
else if ( size == MCD_SIZE )
return MemoryCardFileType :: PS1 ;
else
return MemoryCardFileType :: Unknown ;
}
static bool IsMemoryCardFolder ( const std :: string & path )
{
2022-05-20 00:46:33 +10:00
const std :: string superblock_path ( Path :: Combine ( path , s_folder_mem_card_id_file ));
2022-01-15 05:43:15 +10:00
return FileSystem :: FileExists ( superblock_path . c_str ());
}
2022-03-25 19:23:54 +10:00
static bool IsMemoryCardFormatted ( const std :: string & path )
{
2022-06-06 22:47:09 +10:00
auto fp = FileSystem :: OpenManagedSharedCFile ( path . c_str (), "rb" , FileSystem :: FileShareMode :: DenyNone );
2022-03-25 19:23:54 +10:00
if ( ! fp )
return false ;
static const char formatted_psx [] = "MC" ;
static const char formatted_string [] = "Sony PS2 Memory Card Format" ;
static constexpr size_t read_length = sizeof ( formatted_string ) - 1 ;
u8 data [ read_length ];
if ( std :: fread ( data , read_length , 1 , fp . get ()) != 1 )
return false ;
return ( std :: memcmp ( data , formatted_string , sizeof ( formatted_string ) - 1 ) == 0 ||
std :: memcmp ( data , formatted_psx , sizeof ( formatted_psx ) - 1 ) == 0 );
}
2022-01-15 05:43:15 +10:00
std :: vector < AvailableMcdInfo > FileMcd_GetAvailableCards ( bool include_in_use_cards )
{
std :: vector < FILESYSTEM_FIND_DATA > files ;
2022-05-20 00:46:33 +10:00
FileSystem :: FindFiles ( EmuFolders :: MemoryCards . c_str (), "*" ,
2022-01-15 05:43:15 +10:00
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES , & files );
std :: vector < AvailableMcdInfo > mcds ;
mcds . reserve ( files . size ());
for ( FILESYSTEM_FIND_DATA & fd : files )
{
2022-05-20 00:46:33 +10:00
std :: string basename ( Path :: GetFileName ( fd . FileName ));
2022-01-15 05:43:15 +10:00
if ( ! include_in_use_cards )
{
bool in_use = false ;
for ( size_t i = 0 ; i < std :: size ( EmuConfig . Mcd ); i ++ )
{
if ( EmuConfig . Mcd [ i ]. Filename == basename )
{
in_use = true ;
break ;
}
}
if ( in_use )
continue ;
}
if ( fd . Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY )
{
if ( ! IsMemoryCardFolder ( fd . FileName ))
continue ;
2022-03-25 19:23:54 +10:00
mcds . push_back ({ std :: move ( basename ), std :: move ( fd . FileName ), fd . ModificationTime ,
MemoryCardType :: Folder , MemoryCardFileType :: Unknown , 0u , true });
2022-01-15 05:43:15 +10:00
}
else
{
if ( fd . Size < MCD_SIZE )
continue ;
2022-03-25 19:23:54 +10:00
const bool formatted = IsMemoryCardFormatted ( fd . FileName );
mcds . push_back ({ std :: move ( basename ), std :: move ( fd . FileName ), fd . ModificationTime ,
MemoryCardType :: File , GetMemoryCardFileTypeFromSize ( fd . Size ),
static_cast < u32 > ( fd . Size ), formatted });
2022-01-15 05:43:15 +10:00
}
}
return mcds ;
}
std :: optional < AvailableMcdInfo > FileMcd_GetCardInfo ( const std :: string_view & name )
{
std :: optional < AvailableMcdInfo > ret ;
std :: string basename ( name );
2022-05-20 00:46:33 +10:00
std :: string path ( Path :: Combine ( EmuFolders :: MemoryCards , basename ));
2022-01-15 05:43:15 +10:00
FILESYSTEM_STAT_DATA sd ;
if ( ! FileSystem :: StatFile ( path . c_str (), & sd ))
return ret ;
if ( sd . Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY )
{
if ( IsMemoryCardFolder ( path ))
{
2022-03-25 19:23:54 +10:00
ret = { std :: move ( basename ), std :: move ( path ), sd . ModificationTime ,
MemoryCardType :: Folder , MemoryCardFileType :: Unknown , 0u , true };
2022-01-15 05:43:15 +10:00
}
}
else
{
if ( sd . Size >= MCD_SIZE )
{
2022-03-25 19:23:54 +10:00
const bool formatted = IsMemoryCardFormatted ( path );
ret = { std :: move ( basename ), std :: move ( path ), sd . ModificationTime ,
MemoryCardType :: File , GetMemoryCardFileTypeFromSize ( sd . Size ),
static_cast < u32 > ( sd . Size ), formatted };
2022-01-15 05:43:15 +10:00
}
}
return ret ;
}
bool FileMcd_CreateNewCard ( const std :: string_view & name , MemoryCardType type , MemoryCardFileType file_type )
{
2022-05-20 00:46:33 +10:00
const std :: string full_path ( Path :: Combine ( EmuFolders :: MemoryCards , name ));
2022-01-15 05:43:15 +10:00
if ( type == MemoryCardType :: Folder )
{
2022-03-20 13:40:47 +10:00
Console . WriteLn ( "(FileMcd) Creating new PS2 folder memory card: '%.*s'" , static_cast < int > ( name . size ()), name . data ());
2022-01-15 05:43:15 +10:00
if ( ! FileSystem :: CreateDirectoryPath ( full_path . c_str (), false ))
{
Host :: ReportFormattedErrorAsync ( "Memory Card Creation Failed" , "Failed to create directory '%s'." , full_path . c_str ());
return false ;
}
// write the superblock
2022-05-20 00:46:33 +10:00
auto fp = FileSystem :: OpenManagedCFile ( Path :: Combine ( full_path , s_folder_mem_card_id_file ). c_str (), "wb" );
2022-01-15 05:43:15 +10:00
if ( ! fp )
{
Host :: ReportFormattedErrorAsync ( "Memory Card Creation Failed" , "Failed to write memory card folder superblock '%s'." , full_path . c_str ());
return false ;
}
return true ;
}
if ( type == MemoryCardType :: File )
{
if ( file_type <= MemoryCardFileType :: Unknown || file_type >= MemoryCardFileType :: MaxCount )
return false ;
static constexpr std :: array < u32 , static_cast < size_t > ( MemoryCardFileType :: MaxCount ) > sizes = {{ 0 , 8 * MC2_MBSIZE , 16 * MC2_MBSIZE , 32 * MC2_MBSIZE , 64 * MC2_MBSIZE , MCD_SIZE }};
const bool isPSX = ( type == MemoryCardType :: File && file_type == MemoryCardFileType :: PS1 );
const u32 size = sizes [ static_cast < u32 > ( file_type )];
if ( ! isPSX && size == 0 )
return false ;
auto fp = FileSystem :: OpenManagedCFile ( full_path . c_str (), "wb" );
if ( ! fp )
{
Host :: ReportFormattedErrorAsync ( "Memory Card Creation Failed" , "Failed to open file '%s'." , full_path . c_str ());
return false ;
}
if ( ! isPSX )
{
Console . WriteLn ( "(FileMcd) Creating new PS2 %uMB memory card: '%s'" , size / MC2_MBSIZE , full_path . c_str ());
// PS2 Memory Card
u8 m_effeffs [ 528 * 16 ];
memset8 < 0xff > ( m_effeffs );
const u32 count = size / sizeof ( m_effeffs );
for ( uint i = 0 ; i < count ; i ++ )
{
if ( std :: fwrite ( m_effeffs , sizeof ( m_effeffs ), 1 , fp . get ()) != 1 )
{
Host :: ReportFormattedErrorAsync ( "Memory Card Creation Failed" , "Failed to write file '%s'." , full_path . c_str ());
return false ;
}
}
return true ;
}
else
{
Console . WriteLn ( "(FileMcd) Creating new PSX 128 KiB memory card: '%s'" , full_path . c_str ());
// PSX Memory Card; 8192 is the size in bytes of a single block of a PSX memory card (8 KiB).
u8 m_effeffs_psx [ 8192 ];
memset8 < 0xff > ( m_effeffs_psx );
// PSX cards consist of 16 blocks, each 8 KiB in size.
for ( uint i = 0 ; i < 16 ; i ++ )
{
if ( std :: fwrite ( m_effeffs_psx , sizeof ( m_effeffs_psx ), 1 , fp . get ()) != 1 )
{
Host :: ReportFormattedErrorAsync ( "Memory Card Creation Failed" , "Failed to write file '%s'." , full_path . c_str ());
return false ;
}
}
return true ;
}
}
return false ;
}
2022-03-25 19:23:54 +10:00
bool FileMcd_RenameCard ( const std :: string_view & name , const std :: string_view & new_name )
{
2022-05-20 00:46:33 +10:00
const std :: string name_path ( Path :: Combine ( EmuFolders :: MemoryCards , name ));
const std :: string new_name_path ( Path :: Combine ( EmuFolders :: MemoryCards , new_name ));
2022-03-25 19:23:54 +10:00
FILESYSTEM_STAT_DATA sd , new_sd ;
if ( ! FileSystem :: StatFile ( name_path . c_str (), & sd ) || FileSystem :: StatFile ( new_name_path . c_str (), & new_sd ))
{
Console . Error ( "(FileMcd) New name already exists, or old name does not" );
return false ;
}
Console . WriteLn ( "(FileMcd) Renaming memory card '%.*s' to '%.*s'" ,
static_cast < int > ( name . size ()), name . data (),
static_cast < int > ( new_name . size ()), new_name . data ());
if ( ! FileSystem :: RenamePath ( name_path . c_str (), new_name_path . c_str ()))
{
Console . Error ( "(FileMcd) Failed to rename '%s' to '%s'" , name_path . c_str (), new_name_path . c_str ());
return false ;
}
return true ;
}
bool FileMcd_DeleteCard ( const std :: string_view & name )
{
2022-05-20 00:46:33 +10:00
const std :: string name_path ( Path :: Combine ( EmuFolders :: MemoryCards , name ));
2022-03-25 19:23:54 +10:00
FILESYSTEM_STAT_DATA sd ;
if ( ! FileSystem :: StatFile ( name_path . c_str (), & sd ))
{
Console . Error ( "(FileMcd) Can't stat '%s' for deletion" , name_path . c_str ());
return false ;
}
Console . WriteLn ( "(FileMcd) Deleting memory card '%.*s'" , static_cast < int > ( name . size ()), name . data ());
if ( sd . Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY )
{
// must be a folder memcard, so do a recursive delete (scary)
if ( ! FileSystem :: RecursiveDeleteDirectory ( name_path . c_str ()))
{
Console . Error ( "(FileMcd) Failed to recursively delete '%s'" , name_path . c_str ());
return false ;
}
}
else
{
if ( ! FileSystem :: DeleteFilePath ( name_path . c_str ()))
{
Console . Error ( "(FileMcd) Failed to delete file '%s'" , name_path . c_str ());
return false ;
}
}
return true ;
}