diff --git a/Utilities/File.cpp b/Utilities/File.cpp index 424383f952..180cf02cf0 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -1616,6 +1616,116 @@ 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()); + +#ifdef _WIN32 + constexpr auto& delim = "/\\"; +#else + constexpr auto& delim = "/"; +#endif + + auto get_char = [&](std::size_t& from, std::size_t& to, std::size_t count) + { + std::memcpy(&real[to], &path[from], count); + from += count, to += count; + }; + + std::size_t 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; + std::size_t 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 8aef67986c..11576b6c5d 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -498,6 +498,9 @@ namespace fs // Get common cache directory const std::string& get_cache_dir(); + // 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/Cell/Modules/cellGame.cpp b/rpcs3/Emu/Cell/Modules/cellGame.cpp index 4a0d74f91d..7f12d166fa 100644 --- a/rpcs3/Emu/Cell/Modules/cellGame.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGame.cpp @@ -4,6 +4,7 @@ #include "Emu/VFS.h" #include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" +#include "Emu/Cell/lv2/sys_fs.h" #include "cellSysutil.h" #include "cellMsgDialog.h" @@ -555,7 +556,7 @@ error_code cellGameContentPermit(vm::ptr contentInfoPa psf::save_object(fs::file(perm->temp + "/PARAM.SFO", fs::rewrite), perm->sfo); // Make temporary directory persistent (atomically) - if (vfs::host::rename(perm->temp, vfs::get(dir), false)) + if (vfs::host::rename(perm->temp, vfs::get(dir), &g_mp_sys_dev_hdd0, false)) { cellGame.success("cellGameContentPermit(): directory '%s' has been created", dir); diff --git a/rpcs3/Emu/Cell/Modules/cellGifDec.cpp b/rpcs3/Emu/Cell/Modules/cellGifDec.cpp index 248183ea89..458734145c 100644 --- a/rpcs3/Emu/Cell/Modules/cellGifDec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGifDec.cpp @@ -78,11 +78,12 @@ error_code cellGifDecOpen(PMainHandle mainHandle, PPSubHandle subHandle, PSrc sr case CELL_GIFDEC_FILE: { // Get file descriptor and size - fs::file file_s(vfs::get(src->fileName.get_ptr())); + const auto real_path = vfs::get(src->fileName.get_ptr()); + fs::file file_s(real_path); if (!file_s) return CELL_GIFDEC_ERROR_OPEN_FILE; current_subHandle.fileSize = file_s.size(); - current_subHandle.fd = idm::make(src->fileName.get_ptr(), std::move(file_s), 0, 0); + current_subHandle.fd = idm::make(src->fileName.get_ptr(), std::move(file_s), 0, 0, real_path); break; } } diff --git a/rpcs3/Emu/Cell/Modules/cellJpgDec.cpp b/rpcs3/Emu/Cell/Modules/cellJpgDec.cpp index 5a4bab8bb3..9f89928d77 100644 --- a/rpcs3/Emu/Cell/Modules/cellJpgDec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellJpgDec.cpp @@ -69,11 +69,12 @@ error_code cellJpgDecOpen(u32 mainHandle, vm::ptr subHandle, vm::ptrfileName.get_ptr())); + const auto real_path = vfs::get(src->fileName.get_ptr()); + fs::file file_s(real_path); if (!file_s) return CELL_JPGDEC_ERROR_OPEN_FILE; current_subHandle.fileSize = file_s.size(); - current_subHandle.fd = idm::make(src->fileName.get_ptr(), std::move(file_s), 0, 0); + current_subHandle.fd = idm::make(src->fileName.get_ptr(), std::move(file_s), 0, 0, real_path); break; } } diff --git a/rpcs3/Emu/Cell/Modules/cellPngDec.cpp b/rpcs3/Emu/Cell/Modules/cellPngDec.cpp index 9fda6f187d..21f6ab5ad5 100644 --- a/rpcs3/Emu/Cell/Modules/cellPngDec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPngDec.cpp @@ -432,8 +432,10 @@ error_code pngDecOpen(ppu_thread& ppu, PHandle handle, PPStream png_stream, PSrc // Depending on the source type, get the first 8 bytes if (source->srcSelect == CELL_PNGDEC_FILE) { + const auto real_path = vfs::get(stream->source.fileName.get_ptr()); + // Open a file stream - fs::file file_stream(vfs::get(stream->source.fileName.get_ptr())); + fs::file file_stream(real_path); // Check if opening of the PNG file failed if (!file_stream) @@ -450,7 +452,7 @@ error_code pngDecOpen(ppu_thread& ppu, PHandle handle, PPStream png_stream, PSrc } // Get the file descriptor - buffer->fd = idm::make(stream->source.fileName.get_ptr(), std::move(file_stream), 0, 0); + buffer->fd = idm::make(stream->source.fileName.get_ptr(), std::move(file_stream), 0, 0, real_path); // Indicate that we need to read from a file stream buffer->file = true; diff --git a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp index 109e0acf75..aea7fdfbc1 100644 --- a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp @@ -964,7 +964,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v fs::remove_all(old_path); // Remove savedata by renaming - if (!vfs::host::rename(del_path, old_path, false)) + if (!vfs::host::rename(del_path, old_path, &g_mp_sys_dev_hdd0, false)) { fmt::throw_exception("Failed to move directory %s (%s)", del_path, fs::g_tls_error); } @@ -1917,13 +1917,13 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v fs::remove_all(old_path); // Backup old savedata - if (!vfs::host::rename(dir_path, old_path, false)) + if (!vfs::host::rename(dir_path, old_path, &g_mp_sys_dev_hdd0, false)) { fmt::throw_exception("Failed to move directory %s (%s)", dir_path, fs::g_tls_error); } // Commit new savedata - if (!vfs::host::rename(new_path, dir_path, false)) + if (!vfs::host::rename(new_path, dir_path, &g_mp_sys_dev_hdd0, false)) { // TODO: handle the case when only commit failed at the next save load fmt::throw_exception("Failed to move directory %s (%s)", new_path, fs::g_tls_error); diff --git a/rpcs3/Emu/Cell/Modules/cellSysCache.cpp b/rpcs3/Emu/Cell/Modules/cellSysCache.cpp index 7f2f3078bf..b4fe010fd2 100644 --- a/rpcs3/Emu/Cell/Modules/cellSysCache.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSysCache.cpp @@ -71,7 +71,7 @@ struct syscache_info void clear(bool remove_root) noexcept { // Clear cache - if (!vfs::host::remove_all(cache_root + cache_id, cache_root, remove_root)) + if (!vfs::host::remove_all(cache_root + cache_id, cache_root, &g_mp_sys_dev_hdd1, remove_root)) { cellSysutil.fatal("cellSysCache: failed to clear cache directory '%s%s' (%s)", cache_root, cache_id, fs::g_tls_error); } diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 247c12cfb1..5b948da74d 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -230,33 +230,13 @@ error_code sys_fs_test(ppu_thread& ppu, u32 arg1, u32 arg2, vm::ptr arg3, u return CELL_OK; } -lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mode, const void* arg, u64 size) +lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s32 flags, s32 mode, lv2_file_type type, const lv2_fs_mount_point* mp) { - if (vpath.empty()) - { - return {CELL_ENOENT}; - } - - std::string path; - const std::string local_path = vfs::get(vpath, nullptr, &path); - - const auto mp = lv2_fs_object::get_mp(path); - - if (vpath.find_first_not_of('/') == umax) - { - return {CELL_EISDIR, path}; - } - - if (local_path.empty()) - { - return {CELL_ENOTMOUNTED, path}; - } - // TODO: other checks for path if (fs::is_dir(local_path)) { - return {CELL_EISDIR, path}; + return {CELL_EISDIR}; } bs_t open_mode{}; @@ -272,7 +252,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo { if (flags & CELL_FS_O_ACCMODE || flags & (CELL_FS_O_CREAT | CELL_FS_O_TRUNC)) { - return {CELL_EPERM, path}; + return {CELL_EPERM}; } } @@ -326,14 +306,12 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo if (!open_mode) { - fmt::throw_exception("lv2_file::open(%s): Invalid or unimplemented flags: %#o" HERE, path, flags); + fmt::throw_exception("lv2_file::open_raw(): Invalid or unimplemented flags: %#o" HERE, flags); } - fs::file file; - { - std::lock_guard lock(mp->mutex); - file.open(local_path, open_mode); - } + std::lock_guard lock(mp->mutex); + + fs::file file(local_path, open_mode); if (!file && open_mode == fs::read && fs::g_tls_error == fs::error::noent) { @@ -365,7 +343,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo // Failed to create file on read-only FS (file doesn't exist) if (flags & CELL_FS_O_CREAT) { - return {CELL_EROFS, path}; + return {CELL_EROFS}; } } @@ -376,11 +354,11 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo switch (auto error = fs::g_tls_error) { - case fs::error::noent: return {CELL_ENOENT, path}; + case fs::error::noent: return {CELL_ENOENT}; default: sys_fs.error("lv2_file::open(): unknown error %s", error); } - return {CELL_EIO, path}; + return {CELL_EIO}; } if (mp->flags & lv2_mp_flag::read_only) @@ -388,27 +366,27 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo // Failed to create file on read-only FS (file exists) if (flags & CELL_FS_O_CREAT && flags & CELL_FS_O_EXCL) { - return {CELL_EEXIST, path}; + return {CELL_EEXIST}; } // Failed to truncate file on read-only FS if (flags & CELL_FS_O_TRUNC) { - return {CELL_EROFS, path}; + return {CELL_EROFS}; } } if (flags & CELL_FS_O_MSELF && !verify_mself(file)) { - return {CELL_ENOTMSELF, path}; + return {CELL_ENOTMSELF}; } - if (size == 8) + if (type >= lv2_file_type::sdata) { // check for sdata - switch (*static_cast*>(arg)) + switch (type) { - case 0x18000000010: + case lv2_file_type::sdata: { // check if the file has the NPD header, or else assume its not encrypted u32 magic; @@ -419,7 +397,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo auto sdata_file = std::make_unique(std::move(file)); if (!sdata_file->ReadHeader()) { - return {CELL_EFSSPECIFIC, path}; + return {CELL_EFSSPECIFIC}; } file.reset(std::move(sdata_file)); @@ -428,7 +406,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo break; } // edata - case 0x2: + case lv2_file_type::edata: { // check if the file has the NPD header, or else assume its not encrypted u32 magic; @@ -440,7 +418,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo auto sdata_file = std::make_unique(std::move(file), edatkeys->devKlic.load(), edatkeys->rifKey.load()); if (!sdata_file->ReadHeader()) { - return {CELL_EFSSPECIFIC, path}; + return {CELL_EFSSPECIFIC}; } file.reset(std::move(sdata_file)); @@ -452,7 +430,49 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo } } - return {.error = {}, .ppath = path, .file = std::move(file)}; + return {.error = {}, .file = std::move(file)}; +} + +lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mode, const void* arg, u64 size) +{ + if (vpath.empty()) + { + return {CELL_ENOENT}; + } + + std::string path; + const std::string local_path = vfs::get(vpath, nullptr, &path); + + const auto mp = lv2_fs_object::get_mp(path); + + if (vpath.find_first_not_of('/') == umax) + { + return {CELL_EISDIR, path}; + } + + if (local_path.empty()) + { + return {CELL_ENOTMOUNTED, path}; + } + + + lv2_file_type type = lv2_file_type::regular; + + if (size == 8) + { + // see lv2_file::open_raw + switch (*static_cast*>(arg)) + { + case 0x18000000010: type = lv2_file_type::sdata; break; + case 0x2: type = lv2_file_type::edata; break; + default: + break; + } + } + + auto [error, file] = open_raw(local_path, flags, mode, type, mp); + + return {.error = error, .ppath = std::move(path), .real_path = std::move(local_path), .file = std::move(file), .type = type}; } error_code sys_fs_open(ppu_thread& ppu, vm::cptr path, s32 flags, vm::ptr fd, s32 mode, vm::cptr arg, u64 size) @@ -464,7 +484,7 @@ error_code sys_fs_open(ppu_thread& ppu, vm::cptr path, s32 flags, vm::ptr< if (!path) return CELL_EFAULT; - auto [error, ppath, file] = lv2_file::open(path.get_ptr(), flags, mode, arg.get_ptr(), size); + auto [error, ppath, real, file, type] = lv2_file::open(path.get_ptr(), flags, mode, arg.get_ptr(), size); if (error) { @@ -476,42 +496,25 @@ error_code sys_fs_open(ppu_thread& ppu, vm::cptr path, s32 flags, vm::ptr< return {error, path}; } - lv2_file_type type = lv2_file_type::regular; - - if (size == 8) + if (type >= lv2_file_type::sdata) { - // see lv2_file::open - switch (vm::read64(arg.addr())) - { - case 0x18000000010: - case 0x2: - { - type = lv2_file_type::npdrm; - sys_fs.warning("sys_fs_open(): NPDRM detected"); - break; - } - default: - break; - } - } + sys_fs.warning("sys_fs_open(): NPDRM detected"); - if (type == lv2_file_type::npdrm) - { - if (const u32 id = idm::import([&ppath = ppath, &file = file, mode, flags]() -> std::shared_ptr + if (const u32 id = idm::import([&ppath = ppath, &file = file, mode, flags, &real = real, &type = type]() -> std::shared_ptr { if (!g_fxo->get()->npdrm_fds.try_inc(16)) { return nullptr; } - return std::make_shared(ppath, std::move(file), mode, flags, lv2_file_type::npdrm); + return std::make_shared(ppath, std::move(file), mode, flags, real, type); })) { *fd = id; return CELL_OK; } } - else if (const u32 id = idm::make(ppath, std::move(file), mode, flags)) + else if (const u32 id = idm::make(ppath, std::move(file), mode, flags, real)) { *fd = id; return CELL_OK; @@ -622,7 +625,7 @@ error_code sys_fs_close(ppu_thread& ppu, u32 fd) const auto file = idm::withdraw(fd, [](lv2_file& file) { - if (file.type == lv2_file_type::npdrm) + if (file.type >= lv2_file_type::sdata) { g_fxo->get()->npdrm_fds--; } @@ -674,11 +677,9 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr path, vm::ptr fd) return {CELL_ENOTDIR, path}; } - fs::dir dir; - { - std::lock_guard lock(mp->mutex); - dir.open(local_path); - } + std::lock_guard lock(mp->mutex); + + fs::dir dir(local_path); if (!dir) { @@ -1029,9 +1030,10 @@ error_code sys_fs_rename(ppu_thread& ppu, vm::cptr from, vm::cptr to return CELL_EROFS; } - std::lock_guard lock(mp->mutex); + // Done in vfs::host::rename + //std::lock_guard lock(mp->mutex); - if (!vfs::host::rename(local_from, local_to, false)) + if (!vfs::host::rename(local_from, local_to, mp, false)) { switch (auto error = fs::g_tls_error) { @@ -1266,6 +1268,8 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr _arg, u32 return CELL_EBADF; } + std::lock_guard lock(file->mp->mutex); + auto sdata_file = std::make_unique(lv2_file::make_view(file, arg->offset)); if (!sdata_file->ReadHeader()) @@ -1282,7 +1286,7 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr _arg, u32 return nullptr; } - return std::make_shared(file, std::move(stream), file.mode, file.flags, lv2_file_type::npdrm); + return std::make_shared(file, std::move(stream), file.mode, file.flags, file.real_path, lv2_file_type::sdata); })) { arg->out_code = CELL_OK; diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.h b/rpcs3/Emu/Cell/lv2/sys_fs.h index da38317644..b2df9197a1 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.h +++ b/rpcs3/Emu/Cell/lv2/sys_fs.h @@ -3,9 +3,9 @@ #include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/ErrorCodes.h" #include "Utilities/File.h" -#include "Utilities/mutex.h" #include +#include // Open Flags enum : s32 @@ -134,7 +134,8 @@ enum class lv2_mp_flag enum class lv2_file_type { regular = 0, - npdrm, + sdata, + edata, }; struct lv2_fs_mount_point @@ -143,9 +144,17 @@ struct lv2_fs_mount_point const u32 block_size = 4096; const bs_t flags{}; - shared_mutex mutex; + mutable std::recursive_mutex mutex; }; +extern lv2_fs_mount_point g_mp_sys_dev_hdd0; +extern lv2_fs_mount_point g_mp_sys_dev_hdd1; +extern lv2_fs_mount_point g_mp_sys_dev_usb; +extern lv2_fs_mount_point g_mp_sys_dev_bdvd; +extern lv2_fs_mount_point g_mp_sys_app_home; +extern lv2_fs_mount_point g_mp_sys_host_root; +extern lv2_fs_mount_point g_mp_sys_dev_flash; + struct lv2_fs_object { using id_type = lv2_fs_object; @@ -185,41 +194,61 @@ struct lv2_fs_object struct lv2_file final : lv2_fs_object { - const fs::file file; + fs::file file; const s32 mode; const s32 flags; + std::string real_path; const lv2_file_type type; // Stream lock atomic_t lock{0}; - lv2_file(std::string_view filename, fs::file&& file, s32 mode, s32 flags, lv2_file_type type = {}) + // Some variables for convinience of data restoration + struct save_restore_t + { + u64 seek_pos; + u64 atime; + u64 mtime; + } restore_data{}; + + lv2_file(std::string_view filename, fs::file&& file, s32 mode, s32 flags, const std::string& real_path, lv2_file_type type = {}) : lv2_fs_object(lv2_fs_object::get_mp(filename), filename) , file(std::move(file)) , mode(mode) , flags(flags) + , real_path(real_path) , type(type) { } - lv2_file(const lv2_file& host, fs::file&& file, s32 mode, s32 flags, lv2_file_type type = {}) + lv2_file(const lv2_file& host, fs::file&& file, s32 mode, s32 flags, const std::string& real_path, lv2_file_type type = {}) : lv2_fs_object(host.mp, host.name.data()) , file(std::move(file)) , mode(mode) , flags(flags) + , real_path(real_path) , type(type) { } + struct open_raw_result_t + { + CellError error; + fs::file file; + }; + struct open_result_t { CellError error; std::string ppath; + std::string real_path; fs::file file; + lv2_file_type type; }; // Open a file with wrapped logic of sys_fs_open - static open_result_t open(std::string_view path, s32 flags, s32 mode, const void* arg = {}, u64 size = 0); + static open_raw_result_t open_raw(const std::string& path, s32 flags, s32 mode, lv2_file_type type = lv2_file_type::regular, const lv2_fs_mount_point* mp = nullptr); + static open_result_t open(std::string_view vpath, s32 flags, s32 mode, const void* arg = {}, u64 size = 0); // File reading with intermediate buffer static u64 op_read(const fs::file& file, vm::ptr buf, u64 size); diff --git a/rpcs3/Emu/Cell/lv2/sys_overlay.cpp b/rpcs3/Emu/Cell/lv2/sys_overlay.cpp index d3fb31b148..951f878ea8 100644 --- a/rpcs3/Emu/Cell/lv2/sys_overlay.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_overlay.cpp @@ -21,7 +21,7 @@ static error_code overlay_load_module(vm::ptr ovlmid, const std::string& vp { if (!src) { - auto [fs_error, ppath, lv2_file] = lv2_file::open(vpath, 0, 0); + auto [fs_error, ppath, path, lv2_file, type] = lv2_file::open(vpath, 0, 0); if (fs_error) { diff --git a/rpcs3/Emu/Cell/lv2/sys_prx.cpp b/rpcs3/Emu/Cell/lv2/sys_prx.cpp index 1c5090db7a..3d0af518c9 100644 --- a/rpcs3/Emu/Cell/lv2/sys_prx.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_prx.cpp @@ -156,7 +156,7 @@ static error_code prx_load_module(const std::string& vpath, u64 flags, vm::ptr img, vm::c sys_spu.warning("sys_spu_image_open(img=*0x%x, path=%s)", img, path); - auto [fs_error, ppath, file] = lv2_file::open(path.get_ptr(), 0, 0); + auto [fs_error, ppath, path0, file, type] = lv2_file::open(path.get_ptr(), 0, 0); if (fs_error) { diff --git a/rpcs3/Emu/VFS.cpp b/rpcs3/Emu/VFS.cpp index cb08d6d0c9..2f279f0391 100644 --- a/rpcs3/Emu/VFS.cpp +++ b/rpcs3/Emu/VFS.cpp @@ -3,6 +3,8 @@ #include "System.h" #include "VFS.h" +#include "Cell/lv2/sys_fs.h" + #include "Utilities/mutex.h" #include "Utilities/StrUtil.h" @@ -10,6 +12,8 @@ #include #endif +#include + struct vfs_directory { // Real path (empty if root or not exists) @@ -706,18 +710,77 @@ std::string vfs::host::hash_path(const std::string& path, const std::string& dev return fmt::format(u8"%s/$%s%s", dev_root, fmt::base57(std::hash()(path)), fmt::base57(__rdtsc())); } -bool vfs::host::rename(const std::string& from, const std::string& to, bool overwrite) +bool vfs::host::rename(const std::string& from, const std::string& to, const lv2_fs_mount_point* mp, bool overwrite) { - while (!fs::rename(from, to, overwrite)) +#ifdef _WIN32 + constexpr auto& delim = "/\\"; +#else + constexpr auto& delim = "/"; +#endif + + // Lock mount point, close file descriptors, retry + const auto from0 = std::string_view(from).substr(0, from.find_last_not_of(delim) + 1); + const auto escaped_from = fs::escape_path(from); + + // Lock app_home as well because it could be in the same drive as current mount point (TODO) + std::scoped_lock lock(mp->mutex, g_mp_sys_app_home.mutex); + + auto check_path = [&](std::string_view path) { - // Try to ignore access error in order to prevent spurious failure + return path.starts_with(from) && (path.size() == from.size() || path[from.size()] == delim[0] || path[from.size()] == delim[1]); + }; + + idm::select([&](u32 id, lv2_file& file) + { + if (check_path(fs::escape_path(file.real_path))) + { + verify(HERE), file.mp == mp || file.mp == &g_mp_sys_app_home; + file.restore_data.seek_pos = file.file.pos(); + file.file.close(); // Actually close it! + } + }); + + bool res = false; + + for (;; std::this_thread::yield()) + { + if (fs::rename(from, to, overwrite)) + { + res = true; + break; + } + if (Emu.IsStopped() || fs::g_tls_error != fs::error::acces) { - return false; + res = false; + break; } } - return true; + const auto fs_error = fs::g_tls_error; + + idm::select([&](u32 id, lv2_file& file) + { + const auto escaped_real = fs::escape_path(file.real_path); + + if (check_path(escaped_real)) + { + // Update internal path + if (res) + { + file.real_path = to + (escaped_real != escaped_from ? '/' + file.real_path.substr(from0.size()) : ""s); + } + + // Reopen with ignored TRUNC, APPEND, CREATE and EXCL flags + auto res0 = lv2_file::open_raw(file.real_path, file.flags & CELL_FS_O_ACCMODE, file.mode, file.type, file.mp); + file.file = std::move(res0.file); + verify(HERE), file.file.operator bool(); + file.file.seek(file.restore_data.seek_pos); + } + }); + + fs::g_tls_error = fs_error; + return res; } bool vfs::host::unlink(const std::string& path, const std::string& dev_root) @@ -754,7 +817,7 @@ bool vfs::host::unlink(const std::string& path, const std::string& dev_root) #endif } -bool vfs::host::remove_all(const std::string& path, const std::string& dev_root, bool remove_root) +bool vfs::host::remove_all(const std::string& path, const std::string& dev_root, const lv2_fs_mount_point* mp, bool remove_root) { #ifdef _WIN32 if (remove_root) @@ -762,12 +825,12 @@ bool vfs::host::remove_all(const std::string& path, const std::string& dev_root, // Rename to special dummy folder which will be ignored by VFS (but opened file handles can still read or write it) const std::string dummy = hash_path(path, dev_root); - if (!vfs::host::rename(path, dummy, false)) + if (!vfs::host::rename(path, dummy, mp, false)) { return false; } - if (!vfs::host::remove_all(dummy, dev_root, false)) + if (!vfs::host::remove_all(dummy, dev_root, mp, false)) { return false; } @@ -802,7 +865,7 @@ bool vfs::host::remove_all(const std::string& path, const std::string& dev_root, } else { - if (!vfs::host::remove_all(path + '/' + entry.name, dev_root)) + if (!vfs::host::remove_all(path + '/' + entry.name, dev_root, mp)) { return false; } diff --git a/rpcs3/Emu/VFS.h b/rpcs3/Emu/VFS.h index 7e9bc793ac..a7db837630 100644 --- a/rpcs3/Emu/VFS.h +++ b/rpcs3/Emu/VFS.h @@ -4,6 +4,8 @@ #include #include +struct lv2_fs_mount_point; + namespace vfs { // Mount VFS device @@ -24,12 +26,12 @@ namespace vfs std::string hash_path(const std::string& path, const std::string& dev_root); // Call fs::rename with retry on access error - bool rename(const std::string& from, const std::string& to, bool overwrite); + bool rename(const std::string& from, const std::string& to, const lv2_fs_mount_point* mp, bool overwrite); // Delete file without deleting its contents, emulated with MoveFileEx on Windows bool unlink(const std::string& path, const std::string& dev_root); // Delete folder contents using rename, done atomically if remove_root is true - bool remove_all(const std::string& path, const std::string& dev_root, bool remove_root = true); + bool remove_all(const std::string& path, const std::string& dev_root, const lv2_fs_mount_point* mp, bool remove_root = true); } } diff --git a/rpcs3/Loader/TRP.cpp b/rpcs3/Loader/TRP.cpp index 6b954da3b1..d826c92300 100644 --- a/rpcs3/Loader/TRP.cpp +++ b/rpcs3/Loader/TRP.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "Emu/VFS.h" #include "Emu/System.h" +#include "Emu/Cell/lv2/sys_fs.h" #include "TRP.h" #include "Crypto/sha1.h" #include "Utilities/StrUtil.h" @@ -41,7 +42,11 @@ bool TRPLoader::Install(const std::string& dest, bool show) { trp_f.seek(entry.offset); buffer.resize(entry.size); - if (!trp_f.read(buffer)) continue; // ??? + if (!trp_f.read(buffer)) + { + trp_log.error("Failed to read TRPEntry at: offset=0x%x, size=0x%x", entry.offset, entry.size); + continue; // ??? + } // Create the file in the temporary directory success = fs::write_file(temp + vfs::escape(entry.name), fs::create + fs::excl, buffer); @@ -53,7 +58,7 @@ bool TRPLoader::Install(const std::string& dest, bool show) if (success) { - success = vfs::host::remove_all(local_path, Emu.GetHddDir(), true) || !fs::is_dir(local_path); + success = vfs::host::remove_all(local_path, Emu.GetHddDir(), &g_mp_sys_dev_hdd0, true) || !fs::is_dir(local_path); if (success) {