@@ -32,96 +32,64 @@
const int NO_INDEX = -1 ;
static const char * MC_HDR = " MC_SYSTEM_AREA" ;
int GCMemcardDirectory::LoadGCI (const std::string& file_name, bool current_game_only )
bool GCMemcardDirectory::LoadGCI (GCIFile gci )
{
File::IOFile gci_file (file_name, " rb " );
if (gci_file )
// check if any already loaded file has the same internal name as the new file
for ( const GCIFile& already_loaded_gci : m_saves )
{
GCIFile gci;
gci.m_filename = file_name;
gci.m_dirty = false ;
if (!gci_file.ReadBytes (&(gci.m_gci_header ), DENTRY_SIZE))
{
ERROR_LOG (EXPANSIONINTERFACE, " %s failed to read header" , file_name.c_str ());
return NO_INDEX;
}
std::string gci_filename = gci.m_gci_header .GCI_FileName ();
for (u16 i = 0 ; i < m_loaded_saves.size (); ++i)
if (gci.m_gci_header .GCI_FileName () == already_loaded_gci.m_gci_header .GCI_FileName ())
{
if (m_loaded_saves[i] == gci_filename)
{
PanicAlertT (" %s\n was not loaded because it has the same internal filename as previously "
" loaded save\n %s" ,
gci.m_filename .c_str (), m_saves[i].m_filename .c_str ());
return NO_INDEX;
}
ERROR_LOG (EXPANSIONINTERFACE,
" %s\n was not loaded because it has the same internal filename as previously "
" loaded save\n %s" ,
gci.m_filename .c_str (), already_loaded_gci.m_filename .c_str ());
return false ;
}
}
u16 num_blocks = gci.m_gci_header .m_block_count ;
// largest number of free blocks on a memory card
// in reality, there are not likely any valid gci files > 251 blocks
if (num_blocks > 2043 )
{
PanicAlertT (
" %s\n was not loaded because it is an invalid GCI.\n Number of blocks claimed to be %u" ,
gci.m_filename .c_str (), num_blocks);
return NO_INDEX;
}
// check if this file has a valid block size
// 2043 is the largest number of free blocks on a memory card
// in reality, there are not likely any valid gci files > 251 blocks
const u16 num_blocks = gci.m_gci_header .m_block_count ;
if (num_blocks > 2043 )
{
ERROR_LOG (EXPANSIONINTERFACE,
" %s\n was not loaded because it is an invalid GCI.\n Number of blocks claimed to be %u" ,
gci.m_filename .c_str (), num_blocks);
return false ;
}
u32 size = num_blocks * BLOCK_SIZE;
u64 file_size = gci_file.GetSize ();
if (file_size != size + DENTRY_SIZE)
{
PanicAlertT (" %s\n was not loaded because it is an invalid GCI.\n File size (0x%" PRIx64
" ) does not match the size recorded in the header (0x%x)" ,
gci.m_filename .c_str (), file_size, size + DENTRY_SIZE);
return NO_INDEX;
}
if (!gci.LoadSaveBlocks ())
{
ERROR_LOG (EXPANSIONINTERFACE, " Failed to load data of %s" , gci.m_filename .c_str ());
return false ;
}
if (m_game_id == BE32 (gci.m_gci_header .m_gamecode .data ()))
{
gci.LoadSaveBlocks ();
}
else
{
if (current_game_only)
{
return NO_INDEX;
}
int total_blocks = m_hdr.m_size_mb * MBIT_TO_BLOCKS - MC_FST_BLOCKS;
int free_blocks = m_bat1.m_free_blocks ;
if (total_blocks > free_blocks * 10 )
{
PanicAlertT (" %s\n was not loaded because there is less than 10%% free blocks available on "
" the memory card\n "
" Total Blocks: %d; Free Blocks: %d" ,
gci.m_filename .c_str (), total_blocks, free_blocks);
return NO_INDEX;
}
}
u16 first_block = m_bat1.AssignBlocksContiguous (num_blocks);
if (first_block == 0xFFFF )
{
PanicAlertT (
" %s\n was not loaded because there are not enough free blocks on the virtual memory card" ,
file_name.c_str ());
return NO_INDEX;
}
gci.m_gci_header .m_first_block = first_block;
if (gci.HasCopyProtection () && gci.LoadSaveBlocks ())
{
GCMemcard::PSO_MakeSaveGameValid (m_hdr, gci.m_gci_header , gci.m_save_data );
GCMemcard::FZEROGX_MakeSaveGameValid (m_hdr, gci.m_gci_header , gci.m_save_data );
}
int idx = (int )m_saves.size ();
m_dir1.Replace (gci.m_gci_header , idx);
m_saves.push_back (std::move (gci));
SetUsedBlocks (idx);
// reserve storage for the save file in the BAT
u16 first_block = m_bat1.AssignBlocksContiguous (num_blocks);
if (first_block == 0xFFFF )
{
ERROR_LOG (
EXPANSIONINTERFACE,
" %s\n was not loaded because there are not enough free blocks on the virtual memory card" ,
gci.m_filename .c_str ());
return false ;
}
gci.m_gci_header .m_first_block = first_block;
return idx;
if (gci.HasCopyProtection ())
{
GCMemcard::PSO_MakeSaveGameValid (m_hdr, gci.m_gci_header , gci.m_save_data );
GCMemcard::FZEROGX_MakeSaveGameValid (m_hdr, gci.m_gci_header , gci.m_save_data );
}
return NO_INDEX;
// actually load save file into memory card
int idx = (int )m_saves.size ();
m_dir1.Replace (gci.m_gci_header , idx);
m_saves.push_back (std::move (gci));
SetUsedBlocks (idx);
return true ;
}
// This is only used by NetPlay but it made sense to put it here to keep the relevant code together
@@ -187,34 +155,69 @@ GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, int slot, u
File::IOFile ((m_save_directory + MC_HDR), " rb" ).ReadBytes (&m_hdr, BLOCK_SIZE);
}
const bool current_game_only = Config::Get (Config::MAIN_GCI_FOLDER_CURRENT_GAME_ONLY);
std::vector<std::string> filenames = Common::DoFileSearch ({m_save_directory}, {" .gci" });
if (filenames.size () > 112 )
// split up into files for current games we should definitely load,
// and files for other games that we don't care too much about
std::vector<GCIFile> gci_current_game;
std::vector<GCIFile> gci_other_games;
for (const std::string& filename : filenames)
{
Core::DisplayMessage (" Warning: There are more than 112 save files on this memory card.\n "
" Only loading the first 112 in the folder, unless the game ID is the "
" same as the current game's ID" ,
4000 );
GCIFile gci;
gci.m_filename = filename;
gci.m_dirty = false ;
if (!gci.LoadHeader ())
{
ERROR_LOG (EXPANSIONINTERFACE, " Failed to load header of %s" , filename.c_str ());
continue ;
}
if (m_game_id == BE32 (gci.m_gci_header .m_gamecode .data ()))
gci_current_game.emplace_back (std::move (gci));
else if (!current_game_only)
gci_other_games.emplace_back (std::move (gci));
}
for (const std::string& gci_file : filenames)
m_saves.reserve (DIRLEN);
// load files for current game
size_t failed_loads_current_game = 0 ;
for (GCIFile& gci : gci_current_game)
{
if (m_saves.size () == DIRLEN)
{
PanicAlertT (
" There are too many GCI files in the folder\n %s.\n Only the first 127 will be available" ,
m_save_directory.c_str ());
break ;
}
int index = LoadGCI (gci_file, m_saves.size () > 112 ||
Config::Get (Config::MAIN_GCI_FOLDER_CURRENT_GAME_ONLY));
if (index != NO_INDEX)
if (!LoadGCI (std::move (gci)))
{
m_loaded_saves.push_back (m_saves.at (index ).m_gci_header .GCI_FileName ());
// keep track of how many files failed to load for the current game so we can display a
// message to the user informing them why some of their saves may not be loaded
++failed_loads_current_game;
}
}
m_loaded_saves.clear ();
// leave about 10% of free space on the card if possible
const int total_blocks = m_hdr.m_size_mb * MBIT_TO_BLOCKS - MC_FST_BLOCKS;
const int reserved_blocks = total_blocks / 10 ;
// load files for other games
for (GCIFile& gci : gci_other_games)
{
// leave some free file entries for new saves that might be created
if (m_saves.size () > 112 )
break ;
// leave some free blocks for new saves that might be created
const int free_blocks = m_bat1.m_free_blocks ;
const int gci_blocks = gci.m_gci_header .m_block_count ;
if (free_blocks - gci_blocks < reserved_blocks)
continue ;
LoadGCI (std::move (gci));
}
if (failed_loads_current_game > 0 )
{
Core::DisplayMessage (" Warning: Save file(s) of the current game failed to load." , 10000 );
}
m_dir1.FixChecksums ();
m_dir2 = m_dir1;
m_bat2 = m_bat1;
@@ -693,56 +696,6 @@ void GCMemcardDirectory::DoState(PointerWrap& p)
}
}
bool GCIFile::LoadSaveBlocks ()
{
if (m_save_data.empty ())
{
if (m_filename.empty ())
return false ;
File::IOFile save_file (m_filename, " rb" );
if (!save_file)
return false ;
INFO_LOG (EXPANSIONINTERFACE, " Reading savedata from disk for %s" , m_filename.c_str ());
save_file.Seek (DENTRY_SIZE, SEEK_SET);
u16 num_blocks = m_gci_header.m_block_count ;
m_save_data.resize (num_blocks);
if (!save_file.ReadBytes (m_save_data.data (), num_blocks * BLOCK_SIZE))
{
PanicAlertT (" Failed to read data from GCI file %s" , m_filename.c_str ());
m_save_data.clear ();
return false ;
}
}
return true ;
}
int GCIFile::UsesBlock (u16 block_num)
{
for (u16 i = 0 ; i < m_used_blocks.size (); ++i)
{
if (m_used_blocks[i] == block_num)
return i;
}
return -1 ;
}
void GCIFile::DoState (PointerWrap& p)
{
p.DoPOD <DEntry>(m_gci_header);
p.Do (m_dirty);
p.Do (m_filename);
int num_blocks = (int )m_save_data.size ();
p.Do (num_blocks);
m_save_data.resize (num_blocks);
for (auto itr = m_save_data.begin (); itr != m_save_data.end (); ++itr)
{
p.DoPOD <GCMBlock>(*itr);
}
p.Do (m_used_blocks);
}
void MigrateFromMemcardFile (const std::string& directory_name, int card_index)
{
File::CreateFullPath (directory_name);