diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index c54018ee7f..d87bf5323a 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(rpcs3_emu cache_utils.cpp + games_config.cpp IdManager.cpp localized_string.cpp savestate_utils.cpp diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 7b52a1b794..92e56119c0 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -37,7 +37,6 @@ #include "Utilities/StrUtil.h" #include "../Crypto/unself.h" -#include "util/yaml.hpp" #include "util/logs.hpp" #include "util/serialization.hpp" @@ -875,26 +874,6 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool { Init(add_only); - // Load game list (maps ABCD12345 IDs to /dev_bdvd/ locations) - YAML::Node games; - - if (fs::file f{fs::get_config_dir() + "/games.yml", fs::read + fs::create}) - { - auto [result, error] = yaml_load(f.to_string()); - - if (!error.empty()) - { - sys_log.error("Failed to load games.yml: %s", error); - } - - games = result; - } - - if (!games.IsMap()) - { - games.reset(); - } - m_state_inspection_savestate = g_cfg.savestate.state_inspection_mode.get(); m_savestate_extension_flags1 = {}; @@ -956,9 +935,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool m_title_id = disc_info; // Load /dev_bdvd/ from game list if available - if (auto node = games[m_title_id]) + if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty()) { - disc = node.Scalar(); + disc = std::move(game_path); } else if (!g_cfg.savestate.state_inspection_mode) { @@ -1070,9 +1049,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool std::string title_path; // const overload does not create new node on failure - if (auto node = std::as_const(games)[m_title_id]) + if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty()) { - title_path = node.Scalar(); + title_path = std::move(game_path); } for (std::string test_path : @@ -1113,9 +1092,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool title_id = title_id.substr(0, title_id.find_first_of('/')); // Try to load game directory from list if available - if (auto node = (title_id.empty() ? YAML::Node{} : games[title_id])) + if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty()) { - disc = node.Scalar(); + disc = std::move(game_path); m_path = disc + argv[0].substr(game0_path.size() + title_id.size()); } } @@ -1541,9 +1520,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool if ((is_disc_patch || m_cat == "GD") && bdvd_dir.empty() && disc.empty()) { // Load /dev_bdvd/ from game list if available - if (auto node = games[m_title_id]) + if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty()) { - bdvd_dir = node.Scalar(); + bdvd_dir = std::move(game_path); } else { @@ -1552,35 +1531,6 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool } } - auto try_register_game_location = [&](const std::string& key, const std::string& loc) - { - // Access or create node if does not exist - auto node = games[key]; - - if (node && node.Scalar() == loc) - { - // Nothing to do - return true; - } - - // Write to node - node = loc; - - YAML::Emitter out; - out << games; - - fs::pending_file temp(fs::get_config_dir() + "/games.yml"); - - // Do not update games.yml when TITLE_ID is empty - if (temp.file && temp.file.write(out.c_str(), out.size()), temp.commit()) - { - m_games_yml_invalidated = true; - return true; - } - - return false; - }; - // Check /dev_bdvd/ if (disc.empty() && !bdvd_dir.empty() && fs::is_dir(bdvd_dir)) { @@ -1617,7 +1567,11 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool } // Store /dev_bdvd/ location - if (!try_register_game_location(m_title_id, bdvd_dir)) + if (m_games_config.add_game(m_title_id, bdvd_dir)) + { + sys_log.notice("Registered BDVD game directory for title '%s': %s", m_title_id, bdvd_dir); + } + else { sys_log.error("Failed to save BDVD location of title '%s' (error=%s)", m_title_id, fs::g_tls_error); } @@ -1675,7 +1629,11 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool } // Add HG games not in HDD0 to games.yml - if (!try_register_game_location(m_title_id, game_dir)) + if (m_games_config.add_game(m_title_id, game_dir)) + { + sys_log.notice("Registered HG game directory for title '%s': %s", m_title_id, game_dir); + } + else { sys_log.error("Failed to save HG game location of title '%s' (error=%s)", m_title_id, fs::g_tls_error); } @@ -3154,15 +3112,12 @@ void Emulator::AddGamesFromDir(const std::string& path) if (!IsStopped()) return; - m_games_yml_invalidated = false; + m_games_config.set_save_on_dirty(false); // search dropped path first or else the direct parent to an elf is wrongly skipped if (const auto error = BootGame(path, "", false, true); error == game_boot_result::no_errors) { - if (std::exchange(m_games_yml_invalidated, false)) - { - sys_log.notice("Registered game directory: %s", path); - } + // Nothing to do } // search direct subdirectories, that way we can drop one folder containing all games @@ -3177,12 +3132,16 @@ void Emulator::AddGamesFromDir(const std::string& path) if (const auto error = BootGame(dir_path, "", false, true); error == game_boot_result::no_errors) { - if (std::exchange(m_games_yml_invalidated, false)) - { - sys_log.notice("Registered game directory: %s", dir_path); - } + // Nothing to do } } + + m_games_config.set_save_on_dirty(true); + + if (m_games_config.is_dirty() && !m_games_config.save()) + { + sys_log.error("Failed to save games.yml after adding games"); + } } bool Emulator::IsPathInsideDir(std::string_view path, std::string_view dir) const diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 12eda659cd..d90f4c05b3 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -3,6 +3,7 @@ #include "util/types.hpp" #include "util/atomic.hpp" #include "Utilities/bit_set.h" +#include "games_config.h" #include #include #include @@ -119,7 +120,7 @@ class Emulator final atomic_t m_pause_amend_time{0}; // increased when resumed atomic_t m_stop_ctr{0}; // Increments when emulation is stopped - bool m_games_yml_invalidated = false; + games_config m_games_config; video_renderer m_default_renderer; std::string m_default_graphics_adapter; @@ -287,6 +288,11 @@ public: return m_usr; } + const games_config& GetGamesConfig() const + { + return m_games_config; + } + // Get deserialization manager utils::serial* DeserialManager() const; diff --git a/rpcs3/Emu/games_config.cpp b/rpcs3/Emu/games_config.cpp new file mode 100644 index 0000000000..9cbe775cbb --- /dev/null +++ b/rpcs3/Emu/games_config.cpp @@ -0,0 +1,112 @@ +#include "stdafx.h" +#include "games_config.h" +#include "util/logs.hpp" +#include "util/yaml.hpp" +#include "Utilities/File.h" + +LOG_CHANNEL(cfg_log, "CFG"); + +games_config::games_config() +{ + load(); +} + +games_config::~games_config() +{ + if (m_dirty) + { + save(); + } +} + +std::string games_config::get_path(const std::string& title_id) const +{ + if (title_id.empty()) + { + return {}; + } + + if (const auto it = m_games.find(title_id); it != m_games.cend()) + { + return it->second; + } + + return {}; +} + +bool games_config::add_game(const std::string& key, const std::string& path) +{ + // Access or create node if does not exist + if (auto it = m_games.find(key); it != m_games.end()) + { + if (it->second == path) + { + // Nothing to do + return true; + } + + it->second = path; + } + else + { + m_games.emplace(key, path); + } + + m_dirty = true; + + if (m_save_on_dirty) + { + return save(); + } + + return true; +} + +bool games_config::save() +{ + YAML::Emitter out; + out << m_games; + + fs::pending_file temp(fs::get_config_dir() + "/games.yml"); + + if (temp.file && temp.file.write(out.c_str(), out.size()), temp.commit()) + { + m_dirty = false; + return true; + } + + cfg_log.error("Failed to save games.yml: %s", fs::g_tls_error); + return false; +} + +void games_config::load() +{ + m_games.clear(); + + if (fs::file f{fs::get_config_dir() + "/games.yml", fs::read + fs::create}) + { + auto [result, error] = yaml_load(f.to_string()); + + if (!error.empty()) + { + cfg_log.error("Failed to load games.yml: %s", error); + } + + if (!result.IsMap()) + { + if (!result.IsNull()) + { + cfg_log.error("Failed to load games.yml: type %d not a map", result.Type()); + } + return; + } + + for (const auto& entry : result) + { + if (!entry.first.Scalar().empty() && entry.second.IsScalar() && !entry.second.Scalar().empty()) + { + m_games.emplace(entry.first.Scalar(), entry.second.Scalar()); + } + } + } +} diff --git a/rpcs3/Emu/games_config.h b/rpcs3/Emu/games_config.h new file mode 100644 index 0000000000..0415a6d58b --- /dev/null +++ b/rpcs3/Emu/games_config.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +class games_config +{ +public: + games_config(); + virtual ~games_config(); + + void set_save_on_dirty(bool enabled) { m_save_on_dirty = enabled; } + + const std::map& get_games() const { return m_games; } + bool is_dirty() const { return m_dirty; } + + std::string get_path(const std::string& title_id) const; + + bool add_game(const std::string& key, const std::string& path); + bool save(); + +private: + void load(); + + std::map m_games; + + bool m_dirty = false; + bool m_save_on_dirty = true; +}; diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index ff6ae822a4..57509a687c 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -67,6 +67,7 @@ + @@ -497,6 +498,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 32d9305547..31d0ce0a18 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1147,6 +1147,9 @@ Emu\GPU\RSX\Overlays + + Emu + @@ -2311,6 +2314,9 @@ Emu\GPU\RSX\Common + + Emu + diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 3f49ebf5be..2f44e624c2 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -516,28 +516,9 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after) add_dir(_hdd + "game/", false); add_dir(_hdd + "disc/", true); // Deprecated - auto get_games = []() -> YAML::Node + for (const auto& [serial, path] : Emu.GetGamesConfig().get_games()) { - if (const fs::file games = fs::file(fs::get_config_dir() + "/games.yml", fs::read + fs::create)) - { - auto [result, error] = yaml_load(games.to_string()); - - if (!error.empty()) - { - game_list_log.error("Failed to load games.yml: %s", error); - return {}; - } - - return result; - } - - game_list_log.error("Failed to load games.yml, check permissions."); - return {}; - }; - - for (auto&& pair : get_games()) - { - std::string game_dir = pair.second.Scalar(); + std::string game_dir = path; game_dir.resize(game_dir.find_last_not_of('/') + 1); @@ -569,7 +550,7 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after) // Check if the remaining part is the only path component if (frag.find_first_of('/') + 1 == 0) { - game_list_log.trace("Removed duplicate for %s: %s", pair.first.Scalar(), pair.second.Scalar()); + game_list_log.trace("Removed duplicate for %s: %s", serial, path); if (static std::unordered_set warn_once_list; warn_once_list.emplace(game_dir).second) { @@ -589,7 +570,7 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after) } else { - game_list_log.trace("Invalid game path registered for %s: %s", pair.first.Scalar(), pair.second.Scalar()); + game_list_log.trace("Invalid game path registered for %s: %s", serial, path); } }