diff --git a/Utilities/File.cpp b/Utilities/File.cpp index 12083a3904..b8b754c4f1 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -1703,110 +1703,6 @@ bool fs::remove_all(const std::string& path, bool remove_root) 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 result = 0; diff --git a/Utilities/File.h b/Utilities/File.h index ea8fe28582..b8e8b92f91 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -648,9 +648,6 @@ namespace fs 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 bool remove_all(const std::string& path, bool remove_root = true); diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index de207790c6..fe77065494 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -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) { m_force_global_config = force_global_config; + const std::string resolved_path = GetCallbacks().resolve_path(m_path); 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")); } - 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_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; } + // 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 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_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 // 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. 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 if (bdvd_dir.empty() && disc.empty()) { + // Find /USRDIR position +#ifdef _WIN32 + if (const usz usrdir_pos = std::max(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) +#endif { 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 + "/"; // 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) { 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 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 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()) { + auto unescape = [](std::string_view path) + { + // Unescape from host FS + std::vector escaped = fmt::split(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}}); + std::vector 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") { - argv[0] = "/dev_bdvd/PS3_GAME/" + m_path.substr(hdd0_game.size() + 10); - m_dir = "/dev_hdd0/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/" + resolved_path.substr(resolved_hdd0.size(), 10); sys_log.notice("Disc path: %s", m_dir); } else if (from_hdd0_game) { - argv[0] = "/dev_hdd0/game/" + m_path.substr(hdd0_game.size()); - m_dir = "/dev_hdd0/game/" + m_path.substr(hdd0_game.size(), 10); + argv[0] = "/dev_hdd0/game/" + unescape(resolved_path.substr(resolved_hdd0.size())); + m_dir = "/dev_hdd0/game/" + resolved_path.substr(resolved_hdd0.size(), 10); sys_log.notice("Boot path: %s", m_dir); } else if (!bdvd_dir.empty() && fs::is_dir(bdvd_dir)) { // Disc games are on /dev_bdvd/ - const usz pos = m_path.rfind(m_game_dir); - argv[0] = "/dev_bdvd/PS3_GAME/" + m_path.substr(pos + game_dir_size + 1); + const usz pos = resolved_path.rfind(m_game_dir); + argv[0] = "/dev_bdvd/PS3_GAME/" + unescape(resolved_path.substr(pos + game_dir_size + 1)); 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 { - // For homebrew - argv[0] = "/host_root/" + m_path; - m_dir = "/host_root/" + elf_dir + '/'; + // Use /app_home if /host_root is disabled + argv[0] = "/app_home/" + resolved_path.substr(resolved_path.find_last_of(fs::delim) + 1); + m_dir = "/app_home/"; } sys_log.notice("Elf path: %s", argv[0]); diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index f171d08a16..b444004bbb 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -66,6 +66,7 @@ struct EmuCallbacks std::function()> get_trophy_notification_dialog; std::function get_localized_string; std::function get_localized_u32string; + std::string(*resolve_path)(std::string_view) = nullptr; // Resolve path using Qt }; class Emulator final diff --git a/rpcs3/Emu/VFS.cpp b/rpcs3/Emu/VFS.cpp index 9303906b60..3547958723 100644 --- a/rpcs3/Emu/VFS.cpp +++ b/rpcs3/Emu/VFS.cpp @@ -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 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); @@ -746,7 +746,7 @@ bool vfs::host::rename(const std::string& from, const std::string& to, const lv2 idm::select([&](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); @@ -788,7 +788,7 @@ bool vfs::host::rename(const std::string& from, const std::string& to, const lv2 idm::select([&](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)) { diff --git a/rpcs3/headless_application.cpp b/rpcs3/headless_application.cpp index 54297366d5..bc59c4e9d9 100644 --- a/rpcs3/headless_application.cpp +++ b/rpcs3/headless_application.cpp @@ -8,6 +8,8 @@ #include +#include + // For now, a trivial constructor/destructor. May add command line usage later. 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_u32string = [](localized_string_id, const char*) -> std::u32string { return {}; }; + callbacks.resolve_path = [](std::string_view sv) + { + return QFileInfo(QString::fromUtf8(sv.data(), static_cast(sv.size()))).canonicalFilePath().toStdString(); + }; + Emu.SetCallbacks(std::move(callbacks)); } diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index d7c193ad00..1a9b9d8366 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -390,6 +391,11 @@ void gui_application::InitializeCallbacks() return localized_emu::get_u32string(id, args); }; + callbacks.resolve_path = [](std::string_view sv) + { + return QFileInfo(QString::fromUtf8(sv.data(), static_cast(sv.size()))).canonicalFilePath().toStdString(); + }; + Emu.SetCallbacks(std::move(callbacks)); }