From 5b19908996125e7aa5c5d1af37d446f93a4d9e82 Mon Sep 17 00:00:00 2001 From: Nekotekina Date: Wed, 11 Oct 2017 03:19:32 +0300 Subject: [PATCH] Escape problematic characters in VFS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With full-width <>:"\|?* --- rpcs3/Crypto/unpkg.cpp | 11 +- rpcs3/Emu/Cell/Modules/cellSaveData.cpp | 8 +- rpcs3/Emu/Cell/lv2/sys_fs.cpp | 10 +- rpcs3/Emu/VFS.cpp | 233 +++++++++++++++++++++++- rpcs3/Emu/VFS.h | 6 + 5 files changed, 253 insertions(+), 15 deletions(-) diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index b8688ffb6f..1e3e7dbb78 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -4,6 +4,7 @@ #include "sha1.h" #include "key_vault.h" #include "Utilities/StrFmt.h" +#include "Emu/VFS.h" #include "unpkg.h" bool pkg_install(const fs::file& pkg_f, const std::string& dir, atomic_t& sync, const std::string& pkg_filepath) @@ -285,7 +286,7 @@ bool pkg_install(const fs::file& pkg_f, const std::string& dir, atomic_t decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : dec_key.data()); - const std::string name(reinterpret_cast(buf.get()), entry.name_size); + std::string name{reinterpret_cast(buf.get()), entry.name_size}; LOG_NOTICE(LOADER, "Entry 0x%08x: %s", entry.type, name); @@ -303,17 +304,17 @@ bool pkg_install(const fs::file& pkg_f, const std::string& dir, atomic_t case 0x15: case 0x16: { - const std::string path = dir + name; + const std::string path = dir + vfs::escape(name); const bool did_overwrite = fs::is_file(path); - if (did_overwrite && (entry.type&PKG_FILE_ENTRY_OVERWRITE) == 0) + if (did_overwrite && (entry.type & PKG_FILE_ENTRY_OVERWRITE) == 0) { LOG_NOTICE(LOADER, "Didn't overwrite %s", name); break; } - if (fs::file out{ path, fs::rewrite }) + if (fs::file out{path, fs::rewrite}) { for (u64 pos = 0; pos < entry.file_size; pos += BUF_SIZE) { @@ -358,7 +359,7 @@ bool pkg_install(const fs::file& pkg_f, const std::string& dir, atomic_t case PKG_FILE_ENTRY_FOLDER: case 0x12: { - const std::string path = dir + name; + const std::string path = dir + vfs::escape(name); if (fs::create_dir(path)) { diff --git a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp index bfe29f164f..23f5bcfbef 100644 --- a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp @@ -103,13 +103,15 @@ static NEVER_INLINE s32 savedata_op(ppu_thread& ppu, u32 operation, u32 version, const auto prefix_list = fmt::split(setList->dirNamePrefix.get_ptr(), { "|" }); // get the saves matching the supplied prefix - for (const auto& entry : fs::dir(base_dir)) + for (auto&& entry : fs::dir(base_dir)) { if (!entry.is_directory) { continue; } + entry.name = vfs::unescape(entry.name); + for (const auto& prefix : prefix_list) { if (entry.name.substr(0, prefix.size()) == prefix) @@ -453,8 +455,10 @@ static NEVER_INLINE s32 savedata_op(ppu_thread& ppu, u32 operation, u32 version, auto file_list = statGet->fileList.get_ptr(); - for (const auto& entry : fs::dir(dir_path)) + for (auto&& entry : fs::dir(dir_path)) { + entry.name = vfs::unescape(entry.name); + // only files, system files ignored, fileNum is limited by setBuf->fileListMax if (!entry.is_directory && entry.name != "PARAM.SFO" && statGet->fileListNum++ < setBuf->fileListMax) { diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 2fbfc6ff06..154d91f5fe 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -480,9 +480,10 @@ error_code sys_fs_readdir(u32 fd, vm::ptr dir, vm::ptr nread) if (directory->dir.read(info)) { + const std::string vfs_name = vfs::unescape(info.name); dir->d_type = info.is_directory ? CELL_FS_TYPE_DIRECTORY : CELL_FS_TYPE_REGULAR; - dir->d_namlen = u8(std::min(info.name.size(), CELL_FS_MAX_FS_FILE_NAME_LENGTH)); - strcpy_trunc(dir->d_name, info.name); + dir->d_namlen = u8(std::min(vfs_name.size(), CELL_FS_MAX_FS_FILE_NAME_LENGTH)); + strcpy_trunc(dir->d_name, vfs_name); *nread = sizeof(CellFsDirent); } else @@ -1014,9 +1015,10 @@ error_code sys_fs_fcntl(u32 fd, u32 op, vm::ptr _arg, u32 _size) entry.attribute.size = info.size; entry.attribute.blksize = 4096; // ??? + const std::string vfs_name = vfs::unescape(info.name); entry.entry_name.d_type = info.is_directory ? CELL_FS_TYPE_DIRECTORY : CELL_FS_TYPE_REGULAR; - entry.entry_name.d_namlen = u8(std::min(info.name.size(), CELL_FS_MAX_FS_FILE_NAME_LENGTH)); - strcpy_trunc(entry.entry_name.d_name, info.name); + entry.entry_name.d_namlen = u8(std::min(vfs_name.size(), CELL_FS_MAX_FS_FILE_NAME_LENGTH)); + strcpy_trunc(entry.entry_name.d_name, vfs_name); } else { diff --git a/rpcs3/Emu/VFS.cpp b/rpcs3/Emu/VFS.cpp index b56a749847..b063e34365 100644 --- a/rpcs3/Emu/VFS.cpp +++ b/rpcs3/Emu/VFS.cpp @@ -1,4 +1,4 @@ -#include "stdafx.h" +#include "stdafx.h" #include "IdManager.h" #include "VFS.h" @@ -42,7 +42,7 @@ std::string vfs::get(const std::string& vpath, vfs::type _type) return {}; } - return found->second + vpath; + return found->second + vfs::escape(vpath); } if (_type == type::ps3 && match.length(1) == 0) @@ -58,6 +58,231 @@ std::string vfs::get(const std::string& vpath, vfs::type _type) return {}; } - // Concatenate - return found->second + match.str(2); + if (found->second.empty()) + { + // Don't escape /host_root (TODO) + return match.str(2); + } + + // Escape and concatenate + return found->second + vfs::escape(match.str(2)); +} + + +std::string vfs::escape(const std::string& path) +{ + std::string result; + result.reserve(path.size()); + + for (std::size_t i = 0, s = path.size(); i < s; i++) + { + switch (char c = path[i]) + { + case '<': + { + result += u8"<"; + break; + } + case '>': + { + result += u8">"; + break; + } + case ':': + { + result += u8":"; + break; + } + case '"': + { + result += u8"""; + break; + } + case '\\': + { + result += u8"\"; + break; + } + case '|': + { + result += u8"|"; + break; + } + case '?': + { + result += u8"?"; + break; + } + case '*': + { + result += u8"*"; + break; + } + case char{u8"!"[0]}: + { + // Escape full-width characters 0xFF01..0xFF5e with ! (0xFF01) + switch (path[i + 1]) + { + case char{u8"!"[1]}: + { + const uchar c3 = reinterpret_cast(path[i + 2]); + + if (c3 >= 0x81 && c3 <= 0xbf) + { + result += u8"!"; + } + + break; + } + case char{u8"`"[1]}: + { + const uchar c3 = reinterpret_cast(path[i + 2]); + + if (c3 >= 0x80 && c3 <= 0x9e) + { + result += u8"!"; + } + + break; + } + } + + result += c; + break; + } + default: + { + result += c; + break; + } + } + } + + return result; +} + +std::string vfs::unescape(const std::string& path) +{ + std::string result; + result.reserve(path.size()); + + for (std::size_t i = 0, s = path.size(); i < s; i++) + { + switch (char c = path[i]) + { + case char{u8"!"[0]}: + { + switch (path[i + 1]) + { + case char{u8"!"[1]}: + { + const uchar c3 = reinterpret_cast(path[i + 2]); + + if (c3 >= 0x81 && c3 <= 0xbf) + { + switch (path[i + 2]) + { + case char{u8"!"[2]}: + { + i += 3; + result += c; + continue; + } + case char{u8"<"[2]}: + { + result += '<'; + break; + } + case char{u8">"[2]}: + { + result += '>'; + break; + } + case char{u8":"[2]}: + { + result += ':'; + break; + } + case char{u8"""[2]}: + { + result += '"'; + break; + } + case char{u8"\"[2]}: + { + result += '\\'; + break; + } + case char{u8"?"[2]}: + { + result += '?'; + break; + } + case char{u8"*"[2]}: + { + result += '*'; + break; + } + default: + { + // Unrecognized character (ignored) + break; + } + } + + i += 2; + } + else + { + result += c; + } + + break; + } + case char{u8"`"[1]}: + { + const uchar c3 = reinterpret_cast(path[i + 2]); + + if (c3 >= 0x80 && c3 <= 0x9e) + { + switch (path[i + 2]) + { + case char{u8"|"[2]}: + { + result += '|'; + break; + } + default: + { + // Unrecognized character (ignored) + break; + } + } + + i += 2; + } + else + { + result += c; + } + + break; + } + default: + { + result += c; + break; + } + } + break; + } + default: + { + result += c; + break; + } + } + } + + return result; } diff --git a/rpcs3/Emu/VFS.h b/rpcs3/Emu/VFS.h index d78d51eb0b..39e25ef401 100644 --- a/rpcs3/Emu/VFS.h +++ b/rpcs3/Emu/VFS.h @@ -16,4 +16,10 @@ namespace vfs // Convert VFS path to fs path std::string get(const std::string& vpath, type _type = type::ps3); + + // Escape VFS path by replacing non-portable characters with surrogates + std::string escape(const std::string& path); + + // Invert escape operation + std::string unescape(const std::string& path); }