mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-25 12:12:50 +01:00
Emulation: Fix boot path resolving
* Fix /dev_flash executables path arg. (/host_root is wrong for it) * Fix usage of /host_root for homebrew applications when it is not mounted, use /app_home. * Fix path source detection. (don't get fooled by path slashes repetitions, symlinks, '.', ".." and ('\' on Windows)) * Unescape tail of /dev_hdd0 paths.
This commit is contained in:
parent
95725bf7fc
commit
49c5ce30cc
@ -1703,110 +1703,6 @@ bool fs::remove_all(const std::string& path, bool remove_root)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string fs::escape_path(std::string_view path)
|
|
||||||
{
|
|
||||||
std::string real; real.resize(path.size());
|
|
||||||
|
|
||||||
auto get_char = [&](usz& from, usz& to, usz count)
|
|
||||||
{
|
|
||||||
std::memcpy(&real[to], &path[from], count);
|
|
||||||
from += count, to += count;
|
|
||||||
};
|
|
||||||
|
|
||||||
usz i = 0, j = -1, pos_nondelim = 0, after_delim = 0;
|
|
||||||
|
|
||||||
if (i < path.size())
|
|
||||||
{
|
|
||||||
j = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; i < path.size();)
|
|
||||||
{
|
|
||||||
real[j] = path[i];
|
|
||||||
#ifdef _Win32
|
|
||||||
if (real[j] == '\\')
|
|
||||||
{
|
|
||||||
real[j] = '/';
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// If the current character was preceeded by a delimiter special treatment is required:
|
|
||||||
// If another deleimiter is encountered, remove it (do not write it to output string)
|
|
||||||
// Otherwise test if it is a "." or ".." sequence.
|
|
||||||
if (std::exchange(after_delim, path[i] == delim[0] || path[i] == delim[1]))
|
|
||||||
{
|
|
||||||
if (!after_delim)
|
|
||||||
{
|
|
||||||
if (real[j] == '.')
|
|
||||||
{
|
|
||||||
if (i + 1 == path.size())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
get_char(i, j, 1);
|
|
||||||
|
|
||||||
switch (real[j])
|
|
||||||
{
|
|
||||||
case '.':
|
|
||||||
{
|
|
||||||
bool remove_element = true;
|
|
||||||
usz k = 1;
|
|
||||||
|
|
||||||
for (; k + i != path.size(); k++)
|
|
||||||
{
|
|
||||||
switch (path[i + k])
|
|
||||||
{
|
|
||||||
case '.': continue;
|
|
||||||
case delim[0]: case delim[1]: break;
|
|
||||||
default: remove_element = false; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remove_element)
|
|
||||||
{
|
|
||||||
if (i == 1u)
|
|
||||||
{
|
|
||||||
j = pos_nondelim;
|
|
||||||
real[j] = '\0';// Ensure termination at this posistion
|
|
||||||
after_delim = true;
|
|
||||||
i += k;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get_char(i, j, k);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case '/':
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
after_delim = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
default: get_char(i, j, 1); continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pos_nondelim = j;
|
|
||||||
get_char(i, j, 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
get_char(i, j, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j != umax && (real[j] == delim[0] || real[j] == delim[1])) j--; // Do not include a delmiter at the end
|
|
||||||
|
|
||||||
real.resize(j + 1);
|
|
||||||
return real;
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 fs::get_dir_size(const std::string& path, u64 rounding_alignment)
|
u64 fs::get_dir_size(const std::string& path, u64 rounding_alignment)
|
||||||
{
|
{
|
||||||
u64 result = 0;
|
u64 result = 0;
|
||||||
|
@ -648,9 +648,6 @@ namespace fs
|
|||||||
std::string m_dest{}; // Destination file path
|
std::string m_dest{}; // Destination file path
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get real path for comparisons (TODO: investigate std::filesystem::path::compare implementation)
|
|
||||||
std::string escape_path(std::string_view path);
|
|
||||||
|
|
||||||
// Delete directory and all its contents recursively
|
// Delete directory and all its contents recursively
|
||||||
bool remove_all(const std::string& path, bool remove_root = true);
|
bool remove_all(const std::string& path, bool remove_root = true);
|
||||||
|
|
||||||
|
@ -972,6 +972,7 @@ void Emulator::SetForceBoot(bool force_boot)
|
|||||||
game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool force_global_config, bool is_disc_patch)
|
game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool force_global_config, bool is_disc_patch)
|
||||||
{
|
{
|
||||||
m_force_global_config = force_global_config;
|
m_force_global_config = force_global_config;
|
||||||
|
const std::string resolved_path = GetCallbacks().resolve_path(m_path);
|
||||||
|
|
||||||
if (!IsStopped())
|
if (!IsStopped())
|
||||||
{
|
{
|
||||||
@ -1048,7 +1049,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
|
|||||||
_psf = psf::load_object(fs::file(m_sfo_dir + "/PARAM.SFO"));
|
_psf = psf::load_object(fs::file(m_sfo_dir + "/PARAM.SFO"));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_title = std::string(psf::get_string(_psf, "TITLE", std::string_view(m_path).substr(m_path.find_last_of('/') + 1)));
|
m_title = std::string(psf::get_string(_psf, "TITLE", std::string_view(m_path).substr(m_path.find_last_of(fs::delim) + 1)));
|
||||||
m_title_id = std::string(psf::get_string(_psf, "TITLE_ID"));
|
m_title_id = std::string(psf::get_string(_psf, "TITLE_ID"));
|
||||||
m_cat = std::string(psf::get_string(_psf, "CATEGORY"));
|
m_cat = std::string(psf::get_string(_psf, "CATEGORY"));
|
||||||
|
|
||||||
@ -1266,15 +1267,23 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
|
|||||||
return game_boot_result::no_errors;
|
return game_boot_result::no_errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if path is inside the specified directory
|
||||||
|
auto is_path_inside_path = [this](std::string_view path, std::string_view dir)
|
||||||
|
{
|
||||||
|
return (GetCallbacks().resolve_path(path) + '/').starts_with(GetCallbacks().resolve_path(dir) + '/');
|
||||||
|
};
|
||||||
|
|
||||||
// Detect boot location
|
// Detect boot location
|
||||||
constexpr usz game_dir_size = 8; // size of PS3_GAME and PS3_GMXX
|
constexpr usz game_dir_size = 8; // size of PS3_GAME and PS3_GMXX
|
||||||
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 std::string hdd0_disc = vfs::get("/dev_hdd0/disc/");
|
||||||
const bool from_hdd0_game = m_path.starts_with(hdd0_game);
|
const bool from_hdd0_game = is_path_inside_path(m_path, hdd0_game);
|
||||||
|
const bool from_dev_flash = is_path_inside_path(m_path, g_cfg.vfs.get_dev_flash());
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// m_path might be passed from command line with differences in uppercase/lowercase on windows.
|
// m_path might be passed from command line with differences in uppercase/lowercase on windows.
|
||||||
if (!from_hdd0_game && fmt::to_lower(m_path).starts_with(fmt::to_lower(hdd0_game)))
|
if ((!from_hdd0_game && is_path_inside_path(fmt::to_lower(m_path), fmt::to_lower(hdd0_game))) ||
|
||||||
|
(!from_dev_flash && is_path_inside_path(fmt::to_lower(m_path), fmt::to_lower(g_cfg.vfs.get_dev_flash()))))
|
||||||
{
|
{
|
||||||
// Let's just abort to prevent errors down the line.
|
// Let's just abort to prevent errors down the line.
|
||||||
sys_log.error("The boot path seems to contain incorrectly cased characters. Please adjust the path and try again.");
|
sys_log.error("The boot path seems to contain incorrectly cased characters. Please adjust the path and try again.");
|
||||||
@ -1285,7 +1294,12 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
|
|||||||
// Mount /dev_bdvd/ if necessary
|
// Mount /dev_bdvd/ if necessary
|
||||||
if (bdvd_dir.empty() && disc.empty())
|
if (bdvd_dir.empty() && disc.empty())
|
||||||
{
|
{
|
||||||
|
// Find /USRDIR position
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (const usz usrdir_pos = std::max<usz>(elf_dir.rfind("/USRDIR"sv) + 1, elf_dir.rfind("\\USRDIR"sv) + 1) - 1; usrdir_pos != umax)
|
||||||
|
#else
|
||||||
if (const usz usrdir_pos = elf_dir.rfind("/USRDIR"); usrdir_pos != umax)
|
if (const usz usrdir_pos = elf_dir.rfind("/USRDIR"); usrdir_pos != umax)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
const std::string main_dir = elf_dir.substr(0, usrdir_pos);
|
const std::string main_dir = elf_dir.substr(0, usrdir_pos);
|
||||||
|
|
||||||
@ -1313,7 +1327,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
|
|||||||
bdvd_dir = sfb_dir + "/";
|
bdvd_dir = sfb_dir + "/";
|
||||||
|
|
||||||
// Find game dir
|
// Find game dir
|
||||||
if (const std::string main_dir_name = main_dir.substr(main_dir.find_last_of("/\\") + 1);
|
if (const std::string main_dir_name = main_dir.substr(main_dir.find_last_of(fs::delim) + 1);
|
||||||
main_dir_name.size() == game_dir_size)
|
main_dir_name.size() == game_dir_size)
|
||||||
{
|
{
|
||||||
m_game_dir = main_dir_name;
|
m_game_dir = main_dir_name;
|
||||||
@ -1525,7 +1539,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
|
|||||||
// Check game updates
|
// Check game updates
|
||||||
const std::string hdd0_boot = hdd0_game + m_title_id + "/USRDIR/EBOOT.BIN";
|
const std::string hdd0_boot = hdd0_game + m_title_id + "/USRDIR/EBOOT.BIN";
|
||||||
|
|
||||||
if (disc.empty() && !bdvd_dir.empty() && m_path != hdd0_boot && fs::is_file(hdd0_boot))
|
if (disc.empty() && !bdvd_dir.empty() && GetCallbacks().resolve_path(m_path) != GetCallbacks().resolve_path(hdd0_boot) && fs::is_file(hdd0_boot))
|
||||||
{
|
{
|
||||||
// Booting game update
|
// Booting game update
|
||||||
sys_log.success("Updates found at /dev_hdd0/game/%s/", m_title_id);
|
sys_log.success("Updates found at /dev_hdd0/game/%s/", m_title_id);
|
||||||
@ -1638,30 +1652,55 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
|
|||||||
|
|
||||||
if (argv[0].empty())
|
if (argv[0].empty())
|
||||||
{
|
{
|
||||||
|
auto unescape = [](std::string_view path)
|
||||||
|
{
|
||||||
|
// Unescape from host FS
|
||||||
|
std::vector<std::string> escaped = fmt::split(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}});
|
||||||
|
std::vector<std::string> result;
|
||||||
|
for (auto& sv : escaped)
|
||||||
|
result.emplace_back(vfs::unescape(sv));
|
||||||
|
|
||||||
|
return fmt::merge(result, "/");
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string resolved_hdd0 = GetCallbacks().resolve_path(hdd0_game) + '/';
|
||||||
|
|
||||||
if (from_hdd0_game && m_cat == "DG")
|
if (from_hdd0_game && m_cat == "DG")
|
||||||
{
|
{
|
||||||
argv[0] = "/dev_bdvd/PS3_GAME/" + m_path.substr(hdd0_game.size() + 10);
|
argv[0] = "/dev_bdvd/PS3_GAME/" + unescape(resolved_path.substr(resolved_hdd0.size() + 10));
|
||||||
m_dir = "/dev_hdd0/game/" + m_path.substr(hdd0_game.size(), 10);
|
m_dir = "/dev_hdd0/game/" + resolved_path.substr(resolved_hdd0.size(), 10);
|
||||||
sys_log.notice("Disc path: %s", m_dir);
|
sys_log.notice("Disc path: %s", m_dir);
|
||||||
}
|
}
|
||||||
else if (from_hdd0_game)
|
else if (from_hdd0_game)
|
||||||
{
|
{
|
||||||
argv[0] = "/dev_hdd0/game/" + m_path.substr(hdd0_game.size());
|
argv[0] = "/dev_hdd0/game/" + unescape(resolved_path.substr(resolved_hdd0.size()));
|
||||||
m_dir = "/dev_hdd0/game/" + m_path.substr(hdd0_game.size(), 10);
|
m_dir = "/dev_hdd0/game/" + resolved_path.substr(resolved_hdd0.size(), 10);
|
||||||
sys_log.notice("Boot path: %s", m_dir);
|
sys_log.notice("Boot path: %s", m_dir);
|
||||||
}
|
}
|
||||||
else if (!bdvd_dir.empty() && fs::is_dir(bdvd_dir))
|
else if (!bdvd_dir.empty() && fs::is_dir(bdvd_dir))
|
||||||
{
|
{
|
||||||
// Disc games are on /dev_bdvd/
|
// Disc games are on /dev_bdvd/
|
||||||
const usz pos = m_path.rfind(m_game_dir);
|
const usz pos = resolved_path.rfind(m_game_dir);
|
||||||
argv[0] = "/dev_bdvd/PS3_GAME/" + m_path.substr(pos + game_dir_size + 1);
|
argv[0] = "/dev_bdvd/PS3_GAME/" + unescape(resolved_path.substr(pos + game_dir_size + 1));
|
||||||
m_dir = "/dev_bdvd/PS3_GAME/";
|
m_dir = "/dev_bdvd/PS3_GAME/";
|
||||||
}
|
}
|
||||||
|
else if (from_dev_flash)
|
||||||
|
{
|
||||||
|
// Firmware executables
|
||||||
|
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 (g_cfg.vfs.host_root)
|
||||||
|
{
|
||||||
|
// For homebrew
|
||||||
|
argv[0] = "/host_root/" + resolved_path;
|
||||||
|
m_dir = "/host_root/" + elf_dir + '/';
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// For homebrew
|
// Use /app_home if /host_root is disabled
|
||||||
argv[0] = "/host_root/" + m_path;
|
argv[0] = "/app_home/" + resolved_path.substr(resolved_path.find_last_of(fs::delim) + 1);
|
||||||
m_dir = "/host_root/" + elf_dir + '/';
|
m_dir = "/app_home/";
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_log.notice("Elf path: %s", argv[0]);
|
sys_log.notice("Elf path: %s", argv[0]);
|
||||||
|
@ -66,6 +66,7 @@ struct EmuCallbacks
|
|||||||
std::function<std::unique_ptr<class TrophyNotificationBase>()> get_trophy_notification_dialog;
|
std::function<std::unique_ptr<class TrophyNotificationBase>()> get_trophy_notification_dialog;
|
||||||
std::function<std::string(localized_string_id, const char*)> get_localized_string;
|
std::function<std::string(localized_string_id, const char*)> get_localized_string;
|
||||||
std::function<std::u32string(localized_string_id, const char*)> get_localized_u32string;
|
std::function<std::u32string(localized_string_id, const char*)> get_localized_u32string;
|
||||||
|
std::string(*resolve_path)(std::string_view) = nullptr; // Resolve path using Qt
|
||||||
};
|
};
|
||||||
|
|
||||||
class Emulator final
|
class Emulator final
|
||||||
|
@ -735,7 +735,7 @@ bool vfs::host::rename(const std::string& from, const std::string& to, const lv2
|
|||||||
{
|
{
|
||||||
// Lock mount point, close file descriptors, retry
|
// Lock mount point, close file descriptors, retry
|
||||||
const auto from0 = std::string_view(from).substr(0, from.find_last_not_of(fs::delim) + 1);
|
const auto from0 = std::string_view(from).substr(0, from.find_last_not_of(fs::delim) + 1);
|
||||||
const auto escaped_from = fs::escape_path(from);
|
const auto escaped_from = Emu.GetCallbacks().resolve_path(from);
|
||||||
|
|
||||||
std::lock_guard lock(mp->mutex);
|
std::lock_guard lock(mp->mutex);
|
||||||
|
|
||||||
@ -746,7 +746,7 @@ bool vfs::host::rename(const std::string& from, const std::string& to, const lv2
|
|||||||
|
|
||||||
idm::select<lv2_fs_object, lv2_file>([&](u32 /*id*/, lv2_file& file)
|
idm::select<lv2_fs_object, lv2_file>([&](u32 /*id*/, lv2_file& file)
|
||||||
{
|
{
|
||||||
if (check_path(fs::escape_path(file.real_path)))
|
if (check_path(Emu.GetCallbacks().resolve_path(file.real_path)))
|
||||||
{
|
{
|
||||||
ensure(file.mp == mp);
|
ensure(file.mp == mp);
|
||||||
|
|
||||||
@ -788,7 +788,7 @@ bool vfs::host::rename(const std::string& from, const std::string& to, const lv2
|
|||||||
|
|
||||||
idm::select<lv2_fs_object, lv2_file>([&](u32 /*id*/, lv2_file& file)
|
idm::select<lv2_fs_object, lv2_file>([&](u32 /*id*/, lv2_file& file)
|
||||||
{
|
{
|
||||||
const auto escaped_real = fs::escape_path(file.real_path);
|
const auto escaped_real = Emu.GetCallbacks().resolve_path(file.real_path);
|
||||||
|
|
||||||
if (check_path(escaped_real))
|
if (check_path(escaped_real))
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#include <clocale>
|
#include <clocale>
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
// For now, a trivial constructor/destructor. May add command line usage later.
|
// For now, a trivial constructor/destructor. May add command line usage later.
|
||||||
headless_application::headless_application(int& argc, char** argv) : QCoreApplication(argc, argv)
|
headless_application::headless_application(int& argc, char** argv) : QCoreApplication(argc, argv)
|
||||||
{
|
{
|
||||||
@ -107,6 +109,11 @@ void headless_application::InitializeCallbacks()
|
|||||||
callbacks.get_localized_string = [](localized_string_id, const char*) -> std::string { return {}; };
|
callbacks.get_localized_string = [](localized_string_id, const char*) -> std::string { return {}; };
|
||||||
callbacks.get_localized_u32string = [](localized_string_id, const char*) -> std::u32string { return {}; };
|
callbacks.get_localized_u32string = [](localized_string_id, const char*) -> std::u32string { return {}; };
|
||||||
|
|
||||||
|
callbacks.resolve_path = [](std::string_view sv)
|
||||||
|
{
|
||||||
|
return QFileInfo(QString::fromUtf8(sv.data(), static_cast<int>(sv.size()))).canonicalFilePath().toStdString();
|
||||||
|
};
|
||||||
|
|
||||||
Emu.SetCallbacks(std::move(callbacks));
|
Emu.SetCallbacks(std::move(callbacks));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
#include <QLibraryInfo>
|
#include <QLibraryInfo>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
#include <clocale>
|
#include <clocale>
|
||||||
|
|
||||||
@ -390,6 +391,11 @@ void gui_application::InitializeCallbacks()
|
|||||||
return localized_emu::get_u32string(id, args);
|
return localized_emu::get_u32string(id, args);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
callbacks.resolve_path = [](std::string_view sv)
|
||||||
|
{
|
||||||
|
return QFileInfo(QString::fromUtf8(sv.data(), static_cast<int>(sv.size()))).canonicalFilePath().toStdString();
|
||||||
|
};
|
||||||
|
|
||||||
Emu.SetCallbacks(std::move(callbacks));
|
Emu.SetCallbacks(std::move(callbacks));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user