mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-06-06 07:05:04 -06:00
game_list: Fix ISO cache bypass in is_from_yml branch for multi-game ISOs (#18683)
Fixes regression from #18546 and #18679. ## Problem The is_from_yml ISO branch constructed iso_archive unconditionally, bypassing the cache check inside add_game, making the cache write-only for yml-sourced ISOs. ## Fix Added a lightweight index cache entry (iso_path + "//index") storing the subdir list + mtime. On hit, skips archive construction entirely. On miss, walks as before and writes the index
This commit is contained in:
parent
4f23f5505a
commit
d93d9b2c5a
@ -19,17 +19,23 @@ namespace
|
|||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FNV-64 hash of the ISO path used as the cache filename stem.
|
// FNV-64 hash of the given key used as the cache filename stem.
|
||||||
std::string get_cache_stem(const std::string& iso_path)
|
std::string get_cache_stem(std::string_view key)
|
||||||
{
|
{
|
||||||
usz hash = rpcs3::fnv_seed;
|
usz hash = rpcs3::fnv_seed;
|
||||||
for (const char c : iso_path)
|
for (const char c : key)
|
||||||
{
|
{
|
||||||
hash ^= static_cast<u8>(c);
|
hash ^= static_cast<u8>(c);
|
||||||
hash *= rpcs3::fnv_prime;
|
hash *= rpcs3::fnv_prime;
|
||||||
}
|
}
|
||||||
return fmt::format("%016llx", hash);
|
return fmt::format("%016llx", hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Separate stem for the per-ISO subdir index entry.
|
||||||
|
std::string get_index_stem(const std::string& iso_path)
|
||||||
|
{
|
||||||
|
return get_cache_stem(iso_path + "//index");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace iso_cache
|
namespace iso_cache
|
||||||
@ -134,15 +140,96 @@ namespace iso_cache
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool load_index(const std::string& iso_path, std::vector<std::string>& out_subdirs)
|
||||||
|
{
|
||||||
|
fs::stat_t iso_stat{};
|
||||||
|
if (!fs::get_stat(iso_path, iso_stat) || iso_stat.is_directory)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string dir = get_cache_dir();
|
||||||
|
const std::string yml_path = dir + get_index_stem(iso_path) + ".yml";
|
||||||
|
|
||||||
|
const fs::file yml_file(yml_path);
|
||||||
|
if (!yml_file)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto [node, error] = yaml_load(yml_file.to_string());
|
||||||
|
if (!error.empty())
|
||||||
|
{
|
||||||
|
iso_cache_log.warning("Failed to parse index YAML for '%s': %s", iso_path, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const s64 cached_mtime = node["mtime"].as<s64>(0);
|
||||||
|
if (cached_mtime != iso_stat.mtime)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const YAML::Node subdirs_node = node["subdirs"];
|
||||||
|
if (!subdirs_node || !subdirs_node.IsSequence())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& entry : subdirs_node)
|
||||||
|
{
|
||||||
|
std::string name = entry.as<std::string>("");
|
||||||
|
if (!name.empty())
|
||||||
|
{
|
||||||
|
out_subdirs.push_back(std::move(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !out_subdirs.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void save_index(const std::string& iso_path, const std::vector<std::string>& subdirs)
|
||||||
|
{
|
||||||
|
fs::stat_t iso_stat{};
|
||||||
|
if (!fs::get_stat(iso_path, iso_stat))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::string dir = get_cache_dir();
|
||||||
|
const std::string yml_path = dir + get_index_stem(iso_path) + ".yml";
|
||||||
|
|
||||||
|
YAML::Emitter out;
|
||||||
|
out << YAML::BeginMap;
|
||||||
|
out << YAML::Key << "mtime" << YAML::Value << static_cast<long long>(iso_stat.mtime);
|
||||||
|
out << YAML::Key << "subdirs" << YAML::Value << YAML::BeginSeq;
|
||||||
|
for (const std::string& s : subdirs)
|
||||||
|
{
|
||||||
|
out << s;
|
||||||
|
}
|
||||||
|
out << YAML::EndSeq;
|
||||||
|
out << YAML::EndMap;
|
||||||
|
|
||||||
|
if (fs::pending_file yml_file(yml_path); yml_file.file)
|
||||||
|
{
|
||||||
|
yml_file.file.write(out.c_str(), out.size());
|
||||||
|
yml_file.commit();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
iso_cache_log.warning("Failed to write index YAML for '%s'", iso_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void cleanup(const std::unordered_set<std::string>& valid_iso_paths)
|
void cleanup(const std::unordered_set<std::string>& valid_iso_paths)
|
||||||
{
|
{
|
||||||
const std::string dir = get_cache_dir();
|
const std::string dir = get_cache_dir();
|
||||||
|
|
||||||
// Build a set of stems that should exist.
|
// Build a set of stems that should exist, including index entries.
|
||||||
std::unordered_set<std::string> valid_stems;
|
std::unordered_set<std::string> valid_stems;
|
||||||
for (const std::string& path : valid_iso_paths)
|
for (const std::string& path : valid_iso_paths)
|
||||||
{
|
{
|
||||||
valid_stems.insert(get_cache_stem(path));
|
valid_stems.insert(get_cache_stem(path));
|
||||||
|
valid_stems.insert(get_index_stem(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete any cache files whose stem is not in the valid set.
|
// Delete any cache files whose stem is not in the valid set.
|
||||||
|
|||||||
@ -17,6 +17,7 @@ struct iso_metadata_cache_entry
|
|||||||
std::vector<u8> icon_data{};
|
std::vector<u8> icon_data{};
|
||||||
std::string movie_path{};
|
std::string movie_path{};
|
||||||
std::string audio_path{};
|
std::string audio_path{};
|
||||||
|
std::vector<std::string> subdirs{};
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace iso_cache
|
namespace iso_cache
|
||||||
@ -27,6 +28,9 @@ namespace iso_cache
|
|||||||
// Persists a populated cache entry to disk.
|
// Persists a populated cache entry to disk.
|
||||||
void save(const std::string& iso_path, const std::string& cache_key, const iso_metadata_cache_entry& entry);
|
void save(const std::string& iso_path, const std::string& cache_key, const iso_metadata_cache_entry& entry);
|
||||||
|
|
||||||
|
bool load_index(const std::string& iso_path, std::vector<std::string>& out_subdirs);
|
||||||
|
void save_index(const std::string& iso_path, const std::vector<std::string>& subdirs);
|
||||||
|
|
||||||
// Remove cache entries for ISOs that are no longer in the scanned set.
|
// Remove cache entries for ISOs that are no longer in the scanned set.
|
||||||
void cleanup(const std::unordered_set<std::string>& valid_iso_paths);
|
void cleanup(const std::unordered_set<std::string>& valid_iso_paths);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -861,10 +861,22 @@ void game_list_frame::OnParsingFinished()
|
|||||||
{
|
{
|
||||||
if (is_iso_file(entry.path))
|
if (is_iso_file(entry.path))
|
||||||
{
|
{
|
||||||
|
std::vector<std::string> subdirs;
|
||||||
|
|
||||||
|
if (iso_cache::load_index(entry.path, subdirs))
|
||||||
|
{
|
||||||
|
for (const std::string& name : subdirs)
|
||||||
|
{
|
||||||
|
if (m_refresh_watcher.isCanceled()) break;
|
||||||
|
add_game(entry.path, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
iso_archive archive(entry.path);
|
iso_archive archive(entry.path);
|
||||||
const iso_fs_node& root = archive.root();
|
const iso_fs_node& root = archive.root();
|
||||||
const std::regex ps3_gm_regex("^PS3_GM[[:digit:]]{2}$");
|
const std::regex ps3_gm_regex("^PS3_GM[[:digit:]]{2}$");
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
for (const auto& child : root.children)
|
for (const auto& child : root.children)
|
||||||
{
|
{
|
||||||
@ -872,24 +884,26 @@ void game_list_frame::OnParsingFinished()
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!child->metadata.is_directory)
|
if (!child->metadata.is_directory)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& name = child->metadata.name;
|
const std::string& name = child->metadata.name;
|
||||||
|
|
||||||
if (name == "PS3_GAME" || std::regex_match(name, ps3_gm_regex))
|
if (name == "PS3_GAME" || std::regex_match(name, ps3_gm_regex))
|
||||||
{
|
{
|
||||||
|
subdirs.push_back(name);
|
||||||
add_game(entry.path, name);
|
add_game(entry.path, name);
|
||||||
found = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (subdirs.empty())
|
||||||
if (!found)
|
|
||||||
{
|
{
|
||||||
add_game(entry.path);
|
add_game(entry.path);
|
||||||
|
subdirs.push_back("PS3_GAME");
|
||||||
|
}
|
||||||
|
if (!m_refresh_watcher.isCanceled())
|
||||||
|
{
|
||||||
|
iso_cache::save_index(entry.path, subdirs);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user