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.
This commit is contained in:
Admiral H. Curtiss 2019-05-13 23:37:50 +02:00
parent 5af05f6714
commit 15abb1c92d
3 changed files with 125 additions and 110 deletions

View File

@ -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;
m_save_data.resize(num_blocks);
if (!save_file.ReadBytes(m_save_data.data(), num_blocks * BLOCK_SIZE))
const u32 size = num_blocks * BLOCK_SIZE;
u64 file_size = save_file.GetSize();
if (file_size != size + DENTRY_SIZE)
{
PanicAlertT("Failed to read data from GCI file %s", m_filename.c_str());
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);
save_file.Seek(DENTRY_SIZE, SEEK_SET);
if (!save_file.ReadBytes(m_save_data.data(), size))
{
ERROR_LOG(EXPANSIONINTERFACE, "Failed to read data from GCI file %s", m_filename.c_str());
m_save_data.clear();
return false;
}

View File

@ -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;
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;
}
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;
}
}
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;
}
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 (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);
return idx;
}
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;
}
if (!gci.LoadSaveBlocks())
{
ERROR_LOG(EXPANSIONINTERFACE, "Failed to load data of %s", gci.m_filename.c_str());
return false;
}
// 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;
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);
}
// 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)
if (!LoadGCI(std::move(gci)))
{
PanicAlertT(
"There are too many GCI files in the folder\n%s.\nOnly the first 127 will be available",
m_save_directory.c_str());
// 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;
}
}
// 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;
}
int index = LoadGCI(gci_file, m_saves.size() > 112 ||
Config::Get(Config::MAIN_GCI_FOLDER_CURRENT_GAME_ONLY));
if (index != NO_INDEX)
{
m_loaded_saves.push_back(m_saves.at(index).m_gci_header.GCI_FileName());
}
// 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_loaded_saves.clear();
m_dir1.FixChecksums();
m_dir2 = m_dir1;
m_bat2 = m_bat1;

View File

@ -40,7 +40,7 @@ public:
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 @@ private:
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;