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:
parent
8d9dd1d19c
commit
ad3ea966cb
@ -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)
|
bool fs::stat(const std::string& path, stat_t& info)
|
||||||
{
|
{
|
||||||
|
// Ensure consistent information on failure
|
||||||
|
info = {};
|
||||||
|
|
||||||
if (auto device = get_virtual_device(path))
|
if (auto device = get_virtual_device(path))
|
||||||
{
|
{
|
||||||
return device->stat(path, info);
|
return device->stat(path, info);
|
||||||
|
@ -374,7 +374,10 @@ void Emulator::Init(bool add_only)
|
|||||||
if (!fs::create_path(path))
|
if (!fs::create_path(path))
|
||||||
{
|
{
|
||||||
sys_log.fatal("Failed to create path: %s (%s)", path, fs::g_tls_error);
|
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/";
|
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(fs::get_config_dir() + "sounds/");
|
||||||
make_path_verbose(patch_engine::get_patches_path());
|
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)
|
if (add_only)
|
||||||
{
|
{
|
||||||
// We don't need to initialize the rest if we only add games
|
// 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 = rpcs3::utils::get_hdd0_dir();
|
||||||
m_path += std::string_view(argv[0]).substr(9);
|
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))
|
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
|
// Detect boot location
|
||||||
const std::string hdd0_game = vfs::get("/dev_hdd0/game/");
|
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_hdd0_game = IsPathInsideDir(m_path, hdd0_game);
|
||||||
const bool from_dev_flash = IsPathInsideDir(m_path, g_cfg_vfs.get_dev_flash());
|
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
|
// Booting disc game from wrong location
|
||||||
sys_log.error("Disc game %s found at invalid location /dev_hdd0/game/", m_title_id);
|
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());
|
const std::string dst_dir = hdd0_disc + sfb_dir.substr(hdd0_game.size());
|
||||||
|
|
||||||
// Move and retry from correct location
|
// 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")
|
else if (m_cat != "DG" && m_cat != "GD")
|
||||||
{
|
{
|
||||||
// Don't need /dev_bdvd
|
// 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())
|
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());
|
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]) + '/';
|
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)
|
else if (g_cfg.vfs.host_root)
|
||||||
{
|
{
|
||||||
// For homebrew
|
// For homebrew
|
||||||
@ -2899,6 +2945,61 @@ std::set<std::string> Emulator::GetGameDirs() const
|
|||||||
return dirs;
|
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
|
bool Emulator::IsPathInsideDir(std::string_view path, std::string_view dir) const
|
||||||
{
|
{
|
||||||
const std::string dir_path = GetCallbacks().resolve_path(dir);
|
const std::string dir_path = GetCallbacks().resolve_path(dir);
|
||||||
|
@ -329,6 +329,7 @@ public:
|
|||||||
void ConfigurePPUCache() const;
|
void ConfigurePPUCache() const;
|
||||||
|
|
||||||
std::set<std::string> GetGameDirs() const;
|
std::set<std::string> GetGameDirs() const;
|
||||||
|
void AddGamesFromDir(const std::string& path);
|
||||||
|
|
||||||
// Check if path is inside the specified directory
|
// Check if path is inside the specified directory
|
||||||
bool IsPathInsideDir(std::string_view path, std::string_view dir) const;
|
bool IsPathInsideDir(std::string_view path, std::string_view dir) const;
|
||||||
|
@ -251,6 +251,14 @@ namespace psf
|
|||||||
PSF_CHECK(false, corrupt);
|
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
|
#undef PSF_CHECK
|
||||||
return pair;
|
return pair;
|
||||||
}
|
}
|
||||||
|
@ -449,6 +449,11 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
|
|||||||
m_notes.clear();
|
m_notes.clear();
|
||||||
m_games.pop_all();
|
m_games.pop_all();
|
||||||
|
|
||||||
|
if (Emu.IsStopped())
|
||||||
|
{
|
||||||
|
Emu.AddGamesFromDir(fs::get_config_dir() + "/games");
|
||||||
|
}
|
||||||
|
|
||||||
const std::string _hdd = rpcs3::utils::get_hdd0_dir();
|
const std::string _hdd = rpcs3::utils::get_hdd0_dir();
|
||||||
|
|
||||||
const auto add_disc_dir = [&](const std::string& path)
|
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);
|
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/");
|
// Check if a path loaded from games.yml is already registered in add_dir(_hdd + "disc/");
|
||||||
if (game_dir.starts_with(_hdd))
|
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);
|
add_disc_dir(game_dir);
|
||||||
}
|
}
|
||||||
|
else if (has_sfo)
|
||||||
|
{
|
||||||
|
m_path_list.emplace_back(game_dir);
|
||||||
|
}
|
||||||
else
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2871,27 +2871,11 @@ Add valid disc games to gamelist (games.yml)
|
|||||||
void main_window::AddGamesFromDir(const QString& path)
|
void main_window::AddGamesFromDir(const QString& path)
|
||||||
{
|
{
|
||||||
if (!QFileInfo(path).isDir())
|
if (!QFileInfo(path).isDir())
|
||||||
|
{
|
||||||
return;
|
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
|
Emu.AddGamesFromDir(sstr(path));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,7 +43,7 @@ vfs_dialog_path_widget::vfs_dialog_path_widget(const QString& name, const QStrin
|
|||||||
|
|
||||||
QHBoxLayout* selected_config_layout = new QHBoxLayout;
|
QHBoxLayout* selected_config_layout = new QHBoxLayout;
|
||||||
m_selected_config_label = new QLabel(current_path.isEmpty() ? EmptyPath : current_path);
|
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->addWidget(m_selected_config_label);
|
||||||
selected_config_layout->addStretch();
|
selected_config_layout->addStretch();
|
||||||
selected_config_layout->addWidget(add_directory_button);
|
selected_config_layout->addWidget(add_directory_button);
|
||||||
|
Loading…
Reference in New Issue
Block a user