1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-22 02:32:36 +01:00

Add RPCS3/games/ for automatic games detection, support PSN games outside HDD0 (#12982)

* SFO: Do not load PARAM.SFO with illegal TITLE_ID
* Add support for PSN games outside HDD0
* Add RPCS3/games/ for automatic game detection
This commit is contained in:
Elad Ashkenazi 2022-12-02 12:18:07 +02:00 committed by GitHub
parent 8d9dd1d19c
commit ad3ea966cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 22 deletions

View File

@ -437,6 +437,9 @@ std::string fs::get_parent_dir(std::string_view path, u32 levels)
bool fs::stat(const std::string& path, stat_t& info)
{
// Ensure consistent information on failure
info = {};
if (auto device = get_virtual_device(path))
{
return device->stat(path, info);

View File

@ -374,7 +374,10 @@ void Emulator::Init(bool add_only)
if (!fs::create_path(path))
{
sys_log.fatal("Failed to create path: %s (%s)", path, fs::g_tls_error);
return false;
}
return true;
};
const std::string save_path = dev_hdd0 + "home/" + m_usr + "/savedata/";
@ -419,6 +422,11 @@ void Emulator::Init(bool add_only)
make_path_verbose(fs::get_config_dir() + "sounds/");
make_path_verbose(patch_engine::get_patches_path());
if (const std::string games_common = fs::get_config_dir() + "/games"; make_path_verbose(games_common))
{
fs::write_file(user_path + "/Disc Games Can Be Put Here For Automatic Detection.txt", fs::create + fs::excl + fs::write, ""s);
}
if (add_only)
{
// We don't need to initialize the rest if we only add games
@ -961,6 +969,21 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
{
m_path = rpcs3::utils::get_hdd0_dir();
m_path += std::string_view(argv[0]).substr(9);
constexpr auto game0_path = "/dev_hdd0/game/"sv;
if (argv[0].starts_with(game0_path) && !fs::is_file(vfs::get(argv[0])))
{
std::string title_id = argv[0].substr(game0_path.size());
title_id = title_id.substr(0, title_id.find_last_not_of('/'));
// Try to load game directory from list if available
if (auto node = (title_id.empty() ? YAML::Node{} : games[title_id]))
{
disc = node.Scalar();
m_path = disc + argv[0].substr(game0_path.size() + title_id.size());
}
}
}
else if (argv[0].starts_with("/dev_flash"sv))
{
@ -1332,7 +1355,6 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
// Detect boot location
const std::string hdd0_game = vfs::get("/dev_hdd0/game/");
const std::string hdd0_disc = vfs::get("/dev_hdd0/disc/");
const bool from_hdd0_game = IsPathInsideDir(m_path, hdd0_game);
const bool from_dev_flash = IsPathInsideDir(m_path, g_cfg_vfs.get_dev_flash());
@ -1358,6 +1380,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
// Booting disc game from wrong location
sys_log.error("Disc game %s found at invalid location /dev_hdd0/game/", m_title_id);
const std::string hdd0_disc = vfs::get("/dev_hdd0/disc/");
const std::string dst_dir = hdd0_disc + sfb_dir.substr(hdd0_game.size());
// Move and retry from correct location
@ -1483,6 +1506,23 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
else if (m_cat != "DG" && m_cat != "GD")
{
// Don't need /dev_bdvd
if (!m_title_id.empty() && !from_hdd0_game && m_cat == "HG")
{
// Add HG games not in HDD0 to games.yml
games[m_title_id] = m_sfo_dir;
YAML::Emitter out;
out << games;
fs::pending_file temp(fs::get_config_dir() + "/games.yml");
if (!temp.file || temp.file.write(out.c_str(), out.size()), !temp.commit())
{
sys_log.error("Failed to save HG game location of title '%s' (%s)", m_title_id, fs::g_tls_error);
}
vfs::mount("/dev_hdd0/game/" + m_title_id, m_sfo_dir + '/');
}
}
else if (m_cat == "DG" && from_hdd0_game && disc.empty())
{
@ -1812,6 +1852,12 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
argv[0] = "/dev_flash" + resolved_path.substr(GetCallbacks().resolve_path(g_cfg_vfs.get_dev_flash()).size());
m_dir = fs::get_parent_dir(argv[0]) + '/';
}
else if (!m_title_id.empty() && m_cat == "HG")
{
m_dir = "/dev_hdd0/game/" + m_title_id + '/';
argv[0] = m_dir + unescape(resolved_path.substr(GetCallbacks().resolve_path(m_sfo_dir).size()));
sys_log.notice("Boot path: %s", m_dir);
}
else if (g_cfg.vfs.host_root)
{
// For homebrew
@ -2899,6 +2945,61 @@ std::set<std::string> Emulator::GetGameDirs() const
return dirs;
}
void Emulator::AddGamesFromDir(const std::string& path)
{
if (!IsStopped())
return;
const std::string games_yml = fs::get_cache_dir() + "/games.yml";
std::string content_before, content_after;
if (fs::file fd{games_yml})
{
content_before = fd.to_string();
}
// 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 (fs::file fd{games_yml, fs::read + fs::isfile})
{
content_after = fd.to_string();
}
if (content_before != content_after)
{
sys_log.notice("Registered game directory: %s", path);
content_before = content_after;
}
}
// search direct subdirectories, that way we can drop one folder containing all games
for (auto&& dir_entry : fs::dir(path))
{
if (!dir_entry.is_directory || dir_entry.name == "." || dir_entry.name == "..")
{
continue;
}
const std::string dir_path = path + '/' + dir_entry.name;
if (const auto error = BootGame(dir_path, "", false, true); error == game_boot_result::no_errors)
{
if (fs::file fd{games_yml, fs::read + fs::isfile})
{
content_after = fd.to_string();
}
if (content_before != content_after)
{
sys_log.notice("Registered game directory: %s", dir_path);
content_before = content_after;
}
}
}
}
bool Emulator::IsPathInsideDir(std::string_view path, std::string_view dir) const
{
const std::string dir_path = GetCallbacks().resolve_path(dir);

View File

@ -329,6 +329,7 @@ public:
void ConfigurePPUCache() const;
std::set<std::string> GetGameDirs() const;
void AddGamesFromDir(const std::string& path);
// Check if path is inside the specified directory
bool IsPathInsideDir(std::string_view path, std::string_view dir) const;

View File

@ -251,6 +251,14 @@ namespace psf
PSF_CHECK(false, corrupt);
}
const auto tid = get_string(pair.sfo, "TITLE_ID", "");
if (std::find_if(tid.begin(), tid.end(), [](char ch){ return !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')); }) != tid.end())
{
psf_log.error("Invalid title ID ('%s')", tid);
PSF_CHECK(false, corrupt);
}
#undef PSF_CHECK
return pair;
}

View File

@ -449,6 +449,11 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
m_notes.clear();
m_games.pop_all();
if (Emu.IsStopped())
{
Emu.AddGamesFromDir(fs::get_config_dir() + "/games");
}
const std::string _hdd = rpcs3::utils::get_hdd0_dir();
const auto add_disc_dir = [&](const std::string& path)
@ -531,7 +536,14 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
game_dir.resize(game_dir.find_last_not_of('/') + 1);
if (fs::is_file(game_dir + "/PS3_DISC.SFB"))
if (game_dir.empty())
{
continue;
}
const bool has_sfo = fs::is_file(game_dir + "/PARAM.SFO");
if (!has_sfo && fs::is_file(game_dir + "/PS3_DISC.SFB"))
{
// Check if a path loaded from games.yml is already registered in add_dir(_hdd + "disc/");
if (game_dir.starts_with(_hdd))
@ -554,9 +566,13 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
add_disc_dir(game_dir);
}
else if (has_sfo)
{
m_path_list.emplace_back(game_dir);
}
else
{
game_list_log.trace("Invalid disc path registered for %s: %s", pair.first.Scalar(), pair.second.Scalar());
game_list_log.trace("Invalid game path registered for %s: %s", pair.first.Scalar(), pair.second.Scalar());
}
}

View File

@ -2871,27 +2871,11 @@ Add valid disc games to gamelist (games.yml)
void main_window::AddGamesFromDir(const QString& path)
{
if (!QFileInfo(path).isDir())
{
return;
const std::string s_path = sstr(path);
// search dropped path first or else the direct parent to an elf is wrongly skipped
if (const auto error = Emu.BootGame(s_path, "", false, true); error == game_boot_result::no_errors)
{
gui_log.notice("Returned from game addition by drag and drop: %s", s_path);
}
// search direct subdirectories, that way we can drop one folder containing all games
QDirIterator dir_iter(path, QDir::Dirs | QDir::NoDotAndDotDot);
while (dir_iter.hasNext())
{
const std::string dir_path = sstr(dir_iter.next());
if (const auto error = Emu.BootGame(dir_path, "", false, true); error == game_boot_result::no_errors)
{
gui_log.notice("Returned from game addition by drag and drop: %s", dir_path);
}
}
Emu.AddGamesFromDir(sstr(path));
}
/**

View File

@ -43,7 +43,7 @@ vfs_dialog_path_widget::vfs_dialog_path_widget(const QString& name, const QStrin
QHBoxLayout* selected_config_layout = new QHBoxLayout;
m_selected_config_label = new QLabel(current_path.isEmpty() ? EmptyPath : current_path);
selected_config_layout->addWidget(new QLabel(tr("%0 directory:").arg(name)));
selected_config_layout->addWidget(new QLabel(tr("Used %0 directory:").arg(name)));
selected_config_layout->addWidget(m_selected_config_label);
selected_config_layout->addStretch();
selected_config_layout->addWidget(add_directory_button);