Skip to content
Permalink
Browse files

GCMemcardDirectory: Improve logic for which files are loaded into the…

… virtual memory card.

- Files for the current game are now guaranteed to be loaded, space and validity permitting.
- Avoid showing PanicAlerts for every problem encountered, most of them aren't really important enough and will probably just annoy the user.
- And for the only error the user will definitely care about, when the save of the game they're trying to play fails to load, show an imgui message instead.
  • Loading branch information...
AdmiralCurtiss committed May 13, 2019
1 parent 5af05f6 commit 15abb1c92d1972fcecdcec9bb5edb590c2450009
@@ -4,11 +4,12 @@

#include "Core/HW/GCMemcard/GCIFile.h"

#include <cinttypes>

#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/File.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"

bool GCIFile::LoadHeader()
{
@@ -41,12 +42,24 @@ bool GCIFile::LoadSaveBlocks()
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;

const u32 size = num_blocks * BLOCK_SIZE;
u64 file_size = save_file.GetSize();
if (file_size != size + DENTRY_SIZE)
{
ERROR_LOG(EXPANSIONINTERFACE,
"%s\nwas not loaded because it is an invalid GCI.\n File size (0x%" PRIx64
") does not match the size recorded in the header (0x%x)",
m_filename.c_str(), file_size, size + DENTRY_SIZE);
return false;
}

m_save_data.resize(num_blocks);
if (!save_file.ReadBytes(m_save_data.data(), num_blocks * BLOCK_SIZE))
save_file.Seek(DENTRY_SIZE, SEEK_SET);
if (!save_file.ReadBytes(m_save_data.data(), size))
{
PanicAlertT("Failed to read data from GCI file %s", m_filename.c_str());
ERROR_LOG(EXPANSIONINTERFACE, "Failed to read data from GCI file %s", m_filename.c_str());
m_save_data.clear();
return false;
}
@@ -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))
if (gci.m_gci_header.GCI_FileName() == already_loaded_gci.m_gci_header.GCI_FileName())
{
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 (m_loaded_saves[i] == gci_filename)
{
PanicAlertT("%s\nwas 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\nwas 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\nwas 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\nwas not loaded because it is an invalid GCI.\nNumber 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\nwas 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\nwas 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\nwas 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\nwas 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.\nOnly 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;
@@ -40,7 +40,7 @@ class GCMemcardDirectory : public MemoryCardBase
void DoState(PointerWrap& p) override;

private:
int LoadGCI(const std::string& file_name, bool current_game_only);
bool LoadGCI(GCIFile gci);
inline s32 SaveAreaRW(u32 block, bool writing = false);
// s32 DirectoryRead(u32 offset, u32 length, u8* dest_address);
s32 DirectoryWrite(u32 dest_address, u32 length, const u8* src_address);
@@ -56,7 +56,6 @@ class GCMemcardDirectory : public MemoryCardBase
BlockAlloc m_bat1, m_bat2;
std::vector<GCIFile> m_saves;

std::vector<std::string> m_loaded_saves;
std::string m_save_directory;
Common::Event m_flush_trigger;
std::mutex m_write_mutex;

0 comments on commit 15abb1c

Please sign in to comment.
You can’t perform that action at this time.