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:
parent
5af05f6714
commit
15abb1c92d
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 "
|
||||
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(), m_saves[i].m_filename.c_str());
|
||||
return NO_INDEX;
|
||||
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
|
||||
// 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)
|
||||
{
|
||||
PanicAlertT(
|
||||
"%s\nwas not loaded because it is an invalid GCI.\n Number of blocks claimed to be %u",
|
||||
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 NO_INDEX;
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 size = num_blocks * BLOCK_SIZE;
|
||||
u64 file_size = gci_file.GetSize();
|
||||
if (file_size != size + DENTRY_SIZE)
|
||||
if (!gci.LoadSaveBlocks())
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
// reserve storage for the save file in the BAT
|
||||
u16 first_block = m_bat1.AssignBlocksContiguous(num_blocks);
|
||||
if (first_block == 0xFFFF)
|
||||
{
|
||||
PanicAlertT(
|
||||
ERROR_LOG(
|
||||
EXPANSIONINTERFACE,
|
||||
"%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_filename.c_str());
|
||||
return false;
|
||||
}
|
||||
gci.m_gci_header.m_first_block = first_block;
|
||||
if (gci.HasCopyProtection() && gci.LoadSaveBlocks())
|
||||
|
||||
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 idx;
|
||||
}
|
||||
return NO_INDEX;
|
||||
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;
|
||||
}
|
||||
|
||||
for (const std::string& gci_file : filenames)
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue