memcard: Detect and attempt to repair broken index files from the v1.7.2115 bug

This commit is contained in:
Tyler Wilding 2022-03-23 00:33:20 -04:00 committed by refractionpcsx2
parent a4dcaa7c14
commit 2951068e0c
2 changed files with 67 additions and 4 deletions

View File

@ -37,7 +37,8 @@
bool RemoveDirectory(const wxString& dirname); bool RemoveDirectory(const wxString& dirname);
ryml::Tree parseYamlStr(const std::string& str) { ryml::Tree parseYamlStr(const std::string& str)
{
ryml::Callbacks rymlCallbacks = ryml::get_callbacks(); ryml::Callbacks rymlCallbacks = ryml::get_callbacks();
rymlCallbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location loc, void*) { rymlCallbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location loc, void*) {
throw std::runtime_error(fmt::format("[YAML] Parsing error at {}:{} (bufpos={}): {}", throw std::runtime_error(fmt::format("[YAML] Parsing error at {}:{} (bufpos={}): {}",
@ -1293,6 +1294,15 @@ void FolderMemoryCard::FlushFileEntries(const u32 dirCluster, const u32 remainin
else if (!yaml.value().empty()) else if (!yaml.value().empty())
{ {
ryml::NodeRef index = yaml.value().rootref(); ryml::NodeRef index = yaml.value().rootref();
// Detect broken index files, every index file should have atleast ONE child ('[$%]ROOT')
if (!index.has_children())
{
AttemptToRecreateIndexFile(Path::FromWxString(m_folderName.GetFullPath() + subDirPath));
yaml = loadYamlFile(metaFileName.GetFullPath());
index = yaml.value().rootref();
}
ryml::NodeRef entryNode; ryml::NodeRef entryNode;
if (index.has_child("%ROOT")) if (index.has_child("%ROOT"))
{ {
@ -1707,6 +1717,48 @@ void FolderMemoryCard::SetTimeLastWrittenToNow()
m_framesUntilFlush = FramesAfterWriteUntilFlush; m_framesUntilFlush = FramesAfterWriteUntilFlush;
} }
void FolderMemoryCard::AttemptToRecreateIndexFile(fs::path directory) const
{
// Attempt to fix broken index files (potentially broken in v1.7.2115, fixed in 1.7.2307
Console.Error(fmt::format("[Memcard] Folder memory card index file is malformed, backing up and attempting to re-create. This may not work for all games (ie. GTA), so backing up the current index file!. '{}'",
directory.string()));
// This isn't full-proof, so we backup the broken index file
fs::copy_file(directory / "_pcsx2_index", directory / "_pcsx2_index.invalid.bak", fs::copy_options::overwrite_existing);
// Create everything relative to a point in time, with an artifical delay to minimize edge-cases
auto currTime = std::time(nullptr) - 1000;
auto currOrder = 1;
ryml::Tree tree;
ryml::NodeRef root = tree.rootref();
root |= ryml::MAP;
root.append_child() << ryml::key("$ROOT") |= ryml::MAP;
root["$ROOT"]["timeCreated"] << currTime++;
for (const auto& entry : fs::directory_iterator(directory))
{
auto fileName = entry.path().filename().string();
if (entry.is_directory() || fileName.rfind("_pcsx2_", 0) == 0)
{
continue;
}
root.append_child() << ryml::key(fileName) |= ryml::MAP;
ryml::NodeRef newNode = root[c4::to_csubstr(fileName)];
newNode["order"] << currOrder++;
newNode["timeCreated"] << currTime++;
newNode["timeModified"] << currTime++;
}
root["$ROOT"]["timeModified"] << currTime;
auto file = FileSystem::OpenCFile((directory / "_pcsx2_index").string().c_str(), "w");
ryml::emit(tree, file);
std::fflush(file);
std::fclose(file);
}
std::vector<FolderMemoryCard::EnumeratedFileEntry> FolderMemoryCard::GetOrderedFiles(const wxString& dirPath) const std::vector<FolderMemoryCard::EnumeratedFileEntry> FolderMemoryCard::GetOrderedFiles(const wxString& dirPath) const
{ {
std::vector<EnumeratedFileEntry> result; std::vector<EnumeratedFileEntry> result;
@ -1783,13 +1835,23 @@ std::vector<FolderMemoryCard::EnumeratedFileEntry> FolderMemoryCard::GetOrderedF
wxDateTime creationTime, modificationTime; wxDateTime creationTime, modificationTime;
fileInfo.GetTimes(nullptr, &modificationTime, &creationTime); fileInfo.GetTimes(nullptr, &modificationTime, &creationTime);
std::optional<ryml::Tree> yaml = loadYamlFile(wxFileName(fileInfo.GetFullPath(), "_pcsx2_index").GetFullPath()); auto fullPath = wxFileName(fileInfo.GetFullPath(), "_pcsx2_index").GetFullPath();
std::optional<ryml::Tree> yaml = loadYamlFile(fullPath);
EnumeratedFileEntry entry{fileName, creationTime.GetTicks(), modificationTime.GetTicks(), false}; EnumeratedFileEntry entry{fileName, creationTime.GetTicks(), modificationTime.GetTicks(), false};
if (yaml.has_value() && !yaml.value().empty()) if (yaml.has_value() && !yaml.value().empty())
{ {
const ryml::NodeRef indexForDirectory = yaml.value().rootref(); ryml::NodeRef indexForDirectory = yaml.value().rootref();
ryml::NodeRef entryNode;
// Detect broken index files, every index file should have atleast ONE child ('[$%]ROOT')
if (!indexForDirectory.has_children())
{
AttemptToRecreateIndexFile(Path::FromWxString(fileInfo.GetFullPath()));
yaml = loadYamlFile(fullPath);
indexForDirectory = yaml.value().rootref();
}
const ryml::NodeRef entryNode;
if (indexForDirectory.has_child("%ROOT")) if (indexForDirectory.has_child("%ROOT"))
{ {
// NOTE - working around a rapidyaml issue that needs to get resolved upstream // NOTE - working around a rapidyaml issue that needs to get resolved upstream

View File

@ -575,6 +575,7 @@ protected:
void SetTimeLastReadToNow(); void SetTimeLastReadToNow();
void SetTimeLastWrittenToNow(); void SetTimeLastWrittenToNow();
void AttemptToRecreateIndexFile(fs::path directory) const;
wxString GetDisabledMessage(uint slot) const wxString GetDisabledMessage(uint slot) const
{ {