From a8d5a8734a729466ec8432d5b6fa2c6d97b0f8c4 Mon Sep 17 00:00:00 2001 From: Eladash Date: Wed, 31 Mar 2021 00:44:58 +0300 Subject: [PATCH] Loader/cellGame: Do not crash on invalid PSF files --- rpcs3/Emu/Cell/Modules/cellGame.cpp | 16 +++--- rpcs3/Loader/PSF.cpp | 72 ++++++++++++++++++--------- rpcs3/Loader/PSF.h | 23 ++++++++- rpcs3/rpcs3qt/game_list_frame.cpp | 13 +++-- rpcs3/rpcs3qt/save_manager_dialog.cpp | 4 +- 5 files changed, 88 insertions(+), 40 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellGame.cpp b/rpcs3/Emu/Cell/Modules/cellGame.cpp index fccba11922..3dec56f7c5 100644 --- a/rpcs3/Emu/Cell/Modules/cellGame.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGame.cpp @@ -182,9 +182,9 @@ error_code cellHddGameCheck(ppu_thread& ppu, u32 version, vm::cptr dirName const std::string dir = "/dev_hdd0/game/" + game_dir; - psf::registry sfo = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO"))); + auto [sfo, psf_error] = psf::load(vfs::get(dir + "/PARAM.SFO")); - const u32 new_data = sfo.empty() && !fs::is_file(vfs::get(dir + "/PARAM.SFO")) ? CELL_GAMEDATA_ISNEWDATA_YES : CELL_GAMEDATA_ISNEWDATA_NO; + const u32 new_data = psf_error == psf::error::stream ? CELL_GAMEDATA_ISNEWDATA_YES : CELL_GAMEDATA_ISNEWDATA_NO; if (!new_data) { @@ -586,7 +586,7 @@ error_code cellGameDataCheck(u32 type, vm::cptr dirName, vm::ptr dirName, vm::ptr const std::string game_dir = dirName.get_ptr(); const std::string dir = "/dev_hdd0/game/"s + game_dir; - psf::registry sfo = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO"))); + auto [sfo, psf_error] = psf::load(vfs::get(dir + "/PARAM.SFO")); - const u32 new_data = sfo.empty() && !fs::is_file(vfs::get(dir + "/PARAM.SFO")) ? CELL_GAMEDATA_ISNEWDATA_YES : CELL_GAMEDATA_ISNEWDATA_NO; + const u32 new_data = psf_error == psf::error::stream ? CELL_GAMEDATA_ISNEWDATA_YES : CELL_GAMEDATA_ISNEWDATA_NO; if (!new_data) { @@ -961,9 +961,9 @@ error_code cellGameDeleteGameData(vm::cptr dirName) return CELL_GAME_ERROR_NOTSUPPORTED; } - psf::registry sfo = psf::load_object(fs::file(dir + "/PARAM.SFO")); + const auto [sfo, psf_error] = psf::load(dir + "/PARAM.SFO"); - if (psf::get_string(sfo, "CATEGORY") != "GD" && fs::is_file(dir + "/PARAM.SFO")) + if (psf::get_string(sfo, "CATEGORY") != "GD" && psf_error != psf::error::stream) { return CELL_GAME_ERROR_NOTSUPPORTED; } diff --git a/rpcs3/Loader/PSF.cpp b/rpcs3/Loader/PSF.cpp index a76cee22ed..9289072a80 100644 --- a/rpcs3/Loader/PSF.cpp +++ b/rpcs3/Loader/PSF.cpp @@ -21,6 +21,23 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +template<> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto fmt) + { + switch (fmt) + { + case psf::error::stream: return "File doesn't exist"; + case psf::error::not_psf: return "File is not of PSF format"; + case psf::error::corrupt: return "PSF is truncated or corrupted"; + default: break; + } + + return unknown; + }); +} + namespace psf { struct header_t @@ -103,51 +120,52 @@ namespace psf fmt::throw_exception("Invalid format (0x%x)", m_type); } - registry load_object(const fs::file& stream) + load_result_t load(const fs::file& stream) { - registry result; +#define PSF_CHECK(cond, err) if (!static_cast(cond)) { if (error::err != error::stream) psf_log.error("Error loading PSF: %s%s", (errc = error::err), \ + src_loc{__builtin_LINE(), __builtin_COLUMN(), __builtin_FILE(), __builtin_FUNCTION()}); \ + return result.clear(), pair; } - // Hack for empty input (TODO) - if (!stream || !stream.size()) - { - return result; - } + load_result_t pair{}; + auto& [result, errc] = pair; + + PSF_CHECK(stream, stream); stream.seek(0); // Get header header_t header; - ensure(stream.read(header)); + PSF_CHECK(stream.read(header), not_psf); // Check magic and version - ensure(header.magic == "\0PSF"_u32); - ensure(header.version == 0x101u); - ensure(sizeof(header_t) + header.entries_num * sizeof(def_table_t) <= header.off_key_table); - ensure(header.off_key_table <= header.off_data_table); - ensure(header.off_data_table <= stream.size()); + PSF_CHECK(header.magic == "\0PSF"_u32, not_psf); + PSF_CHECK(header.version == 0x101u, not_psf); + PSF_CHECK(header.off_key_table >= sizeof(header_t), corrupt); + PSF_CHECK(header.off_key_table <= header.off_data_table, corrupt); + PSF_CHECK(header.off_data_table <= stream.size(), corrupt); // Get indices std::vector indices; - ensure(stream.read(indices, header.entries_num)); + PSF_CHECK(stream.read(indices, header.entries_num), corrupt); // Get keys std::string keys; - ensure(stream.seek(header.off_key_table) == header.off_key_table); - ensure(stream.read(keys, header.off_data_table - header.off_key_table)); + PSF_CHECK(stream.seek(header.off_key_table) == header.off_key_table, corrupt); + PSF_CHECK(stream.read(keys, header.off_data_table - header.off_key_table), corrupt); // Load entries for (u32 i = 0; i < header.entries_num; ++i) { - ensure(indices[i].key_off < header.off_data_table - header.off_key_table); + PSF_CHECK(indices[i].key_off < header.off_data_table - header.off_key_table, corrupt); // Get key name (null-terminated string) std::string key(keys.data() + indices[i].key_off); // Check entry - ensure(result.count(key) == 0); - ensure(indices[i].param_len <= indices[i].param_max); - ensure(indices[i].data_off < stream.size() - header.off_data_table); - ensure(indices[i].param_max < stream.size() - indices[i].data_off); + PSF_CHECK(result.count(key) == 0, corrupt); + PSF_CHECK(indices[i].param_len <= indices[i].param_max, corrupt); + PSF_CHECK(indices[i].data_off < stream.size() - header.off_data_table, corrupt); + PSF_CHECK(indices[i].param_max < stream.size() - indices[i].data_off, corrupt); // Seek data pointer stream.seek(header.off_data_table + indices[i].data_off); @@ -156,7 +174,7 @@ namespace psf { // Integer data le_t value; - ensure(stream.read(value)); + PSF_CHECK(stream.read(value), corrupt); result.emplace(std::piecewise_construct, std::forward_as_tuple(std::move(key)), @@ -166,7 +184,7 @@ namespace psf { // String/array data std::string value; - ensure(stream.read(value, indices[i].param_len)); + PSF_CHECK(stream.read(value, indices[i].param_len), corrupt); if (indices[i].param_fmt == format::string) { @@ -185,7 +203,13 @@ namespace psf } } - return result; +#undef PSF_CHECK + return pair; + } + + load_result_t load(const std::string& filename) + { + return load(fs::file(filename)); } std::vector save_object(const psf::registry& psf, std::vector&& init) diff --git a/rpcs3/Loader/PSF.h b/rpcs3/Loader/PSF.h index 6e6fea983d..c38e2c6393 100644 --- a/rpcs3/Loader/PSF.h +++ b/rpcs3/Loader/PSF.h @@ -19,6 +19,14 @@ namespace psf integer = 0x0404, }; + enum class error + { + ok, + stream, + not_psf, + corrupt, + }; + class entry final { format m_type{}; @@ -49,8 +57,21 @@ namespace psf // Define PSF registry as a sorted map of entries: using registry = std::map; + struct load_result_t + { + registry sfo; + error errc; + + explicit operator bool() const + { + return !sfo.empty(); + } + }; + // Load PSF registry from SFO binary format - registry load_object(const fs::file&); + load_result_t load(const fs::file&); + load_result_t load(const std::string& filename); + inline registry load_object(const fs::file& f) { return load(f).sfo; } // Convert PSF registry to SFO binary format std::vector save_object(const registry&, std::vector&& init = std::vector{}); diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 4699a87058..065a9311dc 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -550,17 +550,20 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after) { const std::string sfo_dir = Emulator::GetSfoDirFromGamePath(dir, Emu.GetUsr()); - const fs::file sfo_file(sfo_dir + "/PARAM.SFO"); - if (!sfo_file) + + const psf::registry psf = psf::load_object(fs::file(sfo_dir + "/PARAM.SFO")); + + const std::string_view title_id = psf::get_string(psf, "TITLE_ID", ""); + + if (title_id.empty()) { + // Do not care about invalid entries return; } - const auto psf = psf::load_object(sfo_file); - GameInfo game; game.path = dir; - game.serial = std::string(psf::get_string(psf, "TITLE_ID", "")); + game.serial = std::string(title_id); game.name = std::string(psf::get_string(psf, "TITLE", cat_unknown_localized)); game.app_ver = std::string(psf::get_string(psf, "APP_VER", cat_unknown_localized)); game.version = std::string(psf::get_string(psf, "VERSION", cat_unknown_localized)); diff --git a/rpcs3/rpcs3qt/save_manager_dialog.cpp b/rpcs3/rpcs3qt/save_manager_dialog.cpp index 1e9f974217..ce3a6a4f0f 100644 --- a/rpcs3/rpcs3qt/save_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/save_manager_dialog.cpp @@ -55,11 +55,11 @@ namespace } // PSF parameters - const auto& psf = psf::load_object(fs::file(base_dir + entry.name + "/PARAM.SFO")); + const auto [psf, errc] = psf::load(base_dir + entry.name + "/PARAM.SFO"); if (psf.empty()) { - gui_log.error("Failed to load savedata: %s", base_dir + "/" + entry.name); + gui_log.error("Failed to load savedata: %s (%s)", base_dir + "/" + entry.name, errc); continue; }