Skip to content

Commit

Permalink
PSF: Do not crash on invalid PSF files
Browse files Browse the repository at this point in the history
  • Loading branch information
elad335 committed Mar 30, 2021
1 parent e557c96 commit 9c58e35
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 57 deletions.
4 changes: 2 additions & 2 deletions rpcs3/Crypto/unpkg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ bool package_reader::read_param_sfo()

tmp.seek(0);

m_psf = psf::load_object(tmp);
m_psf = psf::load_object(tmp).first;

return true;
}
Expand Down Expand Up @@ -604,7 +604,7 @@ package_error package_reader::check_target_app_version()
return package_error::no_error;
}

const auto installed_psf = psf::load_object(installed_sfo_file);
const auto installed_psf = psf::load_object(installed_sfo_file).first;

const auto installed_title_id = psf::get_string(installed_psf, "TITLE_ID", "");
const auto installed_app_ver = psf::get_string(installed_psf, "APP_VER", "");
Expand Down
23 changes: 13 additions & 10 deletions rpcs3/Emu/Cell/Modules/cellGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ error_code cellHddGameCheck(ppu_thread& ppu, u32 version, vm::cptr<char> dirName

const std::string dir = "/dev_hdd0/game/" + game_dir;

psf::registry sfo = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO")));
psf::registry sfo = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO"))).first;

const u32 new_data = sfo.empty() && !fs::is_file(vfs::get(dir + "/PARAM.SFO")) ? CELL_GAMEDATA_ISNEWDATA_YES : CELL_GAMEDATA_ISNEWDATA_NO;

Expand Down Expand Up @@ -227,7 +227,7 @@ error_code cellHddGameCheck(ppu_thread& ppu, u32 version, vm::cptr<char> dirName
else
{
// TODO: Is cellHddGameCheck really responsible for writing the information in get->getParam ? (If not, delete this else)
const auto& psf = psf::load_object(fs::file(local_dir +"/PARAM.SFO"));
const auto psf = psf::load_object(fs::file(local_dir +"/PARAM.SFO")).first;

// Some following fields may be zero in old FW 1.00 version PARAM.SFO
if (psf.count("PARENTAL_LEVEL") != 0) get->getParam.parentalLevel = psf.at("PARENTAL_LEVEL").as_integer();
Expand Down Expand Up @@ -478,21 +478,23 @@ error_code cellGameBootCheck(vm::ptr<u32> type, vm::ptr<u32> attributes, vm::ptr
*attributes = 0; // TODO
// TODO: dirName might be a read only string when BootCheck is called on a disc game. (e.g. Ben 10 Ultimate Alien: Cosmic Destruction)

sfo = psf::load_object(fs::file(vfs::get("/dev_bdvd/PS3_GAME/PARAM.SFO")));
sfo = psf::load_object(fs::file(vfs::get("/dev_bdvd/PS3_GAME/PARAM.SFO"))).first;
ensure(!sfo.empty());
}
else if (Emu.GetCat() == "GD")
{
*type = CELL_GAME_GAMETYPE_DISC;
*attributes = CELL_GAME_ATTRIBUTE_PATCH; // TODO

sfo = psf::load_object(fs::file(vfs::get(Emu.GetDir() + "PARAM.SFO")));
sfo = psf::load_object(fs::file(vfs::get(Emu.GetDir() + "PARAM.SFO"))).first;
ensure(!sfo.empty());
}
else
{
*type = CELL_GAME_GAMETYPE_HDD;
*attributes = 0; // TODO

sfo = psf::load_object(fs::file(vfs::get(Emu.GetDir() + "PARAM.SFO")));
sfo = psf::load_object(fs::file(vfs::get(Emu.GetDir() + "PARAM.SFO"))).first;
dir = Emu.GetTitleID();
}

Expand Down Expand Up @@ -528,7 +530,8 @@ error_code cellGamePatchCheck(vm::ptr<CellGameContentSize> size, vm::ptr<void> r
return CELL_GAME_ERROR_NOTPATCH;
}

psf::registry sfo = psf::load_object(fs::file(vfs::get(Emu.GetDir() + "PARAM.SFO")));
psf::registry sfo = psf::load_object(fs::file(vfs::get(Emu.GetDir() + "PARAM.SFO"))).first;
ensure(!sfo.empty());

auto& perm = g_fxo->get<content_permission>();

Expand Down Expand Up @@ -586,7 +589,7 @@ error_code cellGameDataCheck(u32 type, vm::cptr<char> dirName, vm::ptr<CellGameC
return CELL_GAME_ERROR_BUSY;
}

auto sfo = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO")));
auto sfo = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO"))).first;

if (psf::get_string(sfo, "CATEGORY") != [&]()
{
Expand Down Expand Up @@ -714,7 +717,7 @@ error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr<char>
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")));
psf::registry sfo = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO"))).first;

const u32 new_data = sfo.empty() && !fs::is_file(vfs::get(dir + "/PARAM.SFO")) ? CELL_GAMEDATA_ISNEWDATA_YES : CELL_GAMEDATA_ISNEWDATA_NO;

Expand Down Expand Up @@ -961,7 +964,7 @@ error_code cellGameDeleteGameData(vm::cptr<char> dirName)
return CELL_GAME_ERROR_NOTSUPPORTED;
}

psf::registry sfo = psf::load_object(fs::file(dir + "/PARAM.SFO"));
psf::registry sfo = psf::load_object(fs::file(dir + "/PARAM.SFO")).first;

if (psf::get_string(sfo, "CATEGORY") != "GD" && fs::is_file(dir + "/PARAM.SFO"))
{
Expand Down Expand Up @@ -1354,7 +1357,7 @@ error_code cellDiscGameGetBootDiscInfo(vm::ptr<CellDiscGameSystemFileParam> getP
return CELL_DISCGAME_ERROR_NOT_DISCBOOT;
}

const auto& psf = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO")));
const auto& psf = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO"))).first;

if (psf.count("PARENTAL_LEVEL") != 0) getParam->parentalLevel = psf.at("PARENTAL_LEVEL").as_integer();
if (psf.count("TITLE_ID") != 0) strcpy_trunc(getParam->titleId, psf.at("TITLE_ID").as_string());
Expand Down
8 changes: 4 additions & 4 deletions rpcs3/Emu/Cell/Modules/cellSaveData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ static std::vector<SaveDataEntry> get_save_entries(const std::string& base_dir,
}

// PSF parameters
const psf::registry psf = psf::load_object(fs::file(base_dir + entry.name + "/PARAM.SFO"));
const psf::registry psf = psf::load_object(fs::file(base_dir + entry.name + "/PARAM.SFO")).first;

if (psf.empty())
{
Expand Down Expand Up @@ -642,7 +642,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
listGet->dirListNum++; // number of directories in list

// PSF parameters
const psf::registry psf = psf::load_object(fs::file(base_dir + entry.name + "/PARAM.SFO"));
const psf::registry psf = psf::load_object(fs::file(base_dir + entry.name + "/PARAM.SFO")).first;

if (psf.empty())
{
Expand Down Expand Up @@ -1244,7 +1244,7 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
const std::string old_path = base_dir + ".backup_" + save_entry.escaped + "/";
const std::string new_path = base_dir + ".working_" + save_entry.escaped + "/";

psf::registry psf = psf::load_object(fs::file(dir_path + "PARAM.SFO"));
psf::registry psf = psf::load_object(fs::file(dir_path + "PARAM.SFO")).first;
bool has_modified = false;
bool recreated = false;

Expand Down Expand Up @@ -2013,7 +2013,7 @@ static NEVER_INLINE error_code savedata_get_list_item(vm::cptr<char> dirName, vm
return CELL_SAVEDATA_ERROR_NODATA;
}

auto psf = psf::load_object(fs::file(sfo));
auto psf = psf::load_object(fs::file(sfo)).first;

if (sysFileParam)
{
Expand Down
16 changes: 8 additions & 8 deletions rpcs3/Emu/System.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,7 @@ std::string Emulator::GetSfoDirFromGamePath(const std::string& game_path, const

if (entry.is_directory && fs::is_file(sfo_path))
{
const auto psf = psf::load_object(fs::file(sfo_path));
const auto psf = psf::load_object(fs::file(sfo_path)).first;
const auto serial = psf::get_string(psf, "TITLE_ID");
if (serial == title_id)
{
Expand All @@ -838,7 +838,7 @@ std::string Emulator::GetSfoDirFromGamePath(const std::string& game_path, const
return game_path + "/PS3_GAME";
}

const auto psf = psf::load_object(fs::file(game_path + "/PARAM.SFO"));
const auto psf = psf::load_object(fs::file(game_path + "/PARAM.SFO")).first;

const auto category = psf::get_string(psf, "CATEGORY");
const auto content_id = std::string(psf::get_string(psf, "CONTENT_ID"));
Expand Down Expand Up @@ -945,7 +945,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
if (fs::file sfov{elf_dir + "/sce_sys/param.sfo"})
{
m_sfo_dir = elf_dir;
_psf = psf::load_object(sfov);
_psf = psf::load_object(sfov).first;
}
else
{
Expand Down Expand Up @@ -975,7 +975,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
m_sfo_dir = GetSfoDirFromGamePath(elf_dir + "/../", GetUsr(), m_title_id);
}

_psf = psf::load_object(fs::file(m_sfo_dir + "/PARAM.SFO"));
_psf = psf::load_object(fs::file(m_sfo_dir + "/PARAM.SFO")).first;
}

m_title = std::string(psf::get_string(_psf, "TITLE", std::string_view(m_path).substr(m_path.find_last_of('/') + 1)));
Expand Down Expand Up @@ -1299,7 +1299,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
return game_boot_result::invalid_file_or_folder;
}

const auto game_psf = psf::load_object(fs::file{vfs::get("/dev_bdvd/PS3_GAME/PARAM.SFO")});
const auto game_psf = psf::load_object(fs::file{vfs::get("/dev_bdvd/PS3_GAME/PARAM.SFO")}).first;
const auto bdvd_title_id = psf::get_string(game_psf, "TITLE_ID");

if (bdvd_title_id != m_title_id)
Expand Down Expand Up @@ -1467,7 +1467,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool

if (!disc_sfo_dir.empty() && fs::is_file(disc_sfo_dir))
{
const auto psf_obj = psf::load_object(fs::file{ disc_sfo_dir });
const auto psf_obj = psf::load_object(fs::file{ disc_sfo_dir }).first;
const auto bdvd_title = psf::get_string(psf_obj, "TITLE");

if (!bdvd_title.empty() && bdvd_title != m_title)
Expand Down Expand Up @@ -2137,8 +2137,8 @@ std::set<std::string> Emulator::GetGameDirs() const
continue;
}

const psf::registry psf = psf::load_object(sfo_file);
const std::string title_id = std::string(psf::get_string(psf, "TITLE_ID", ""));
const psf::registry psf = psf::load_object(sfo_file).first;
const std::string_view title_id = psf::get_string(psf, "TITLE_ID", "");

if (title_id == GetTitleID())
{
Expand Down
66 changes: 42 additions & 24 deletions rpcs3/Loader/PSF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ void fmt_class_string<psf::format>::format(std::string& out, u64 arg)
});
}

template<>
void fmt_class_string<psf::error>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](auto fmt)
{
switch (fmt)
{
case psf::error::stream: return "File is non-existant";
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
Expand Down Expand Up @@ -103,51 +120,51 @@ namespace psf
fmt::throw_exception("Invalid format (0x%x)", m_type);
}

registry load_object(const fs::file& stream)
std::pair<registry, error> load_object(const fs::file& stream)
{
registry result;
#define PSF_CHECK(cond, err) if (!static_cast<bool>(cond)) { psf_log.error("Error loading PSF: %s\n%s", (errc = ::psf::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;
}
std::pair<registry, error> 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 <= header.off_data_table, corrupt);
PSF_CHECK(header.off_data_table <= stream.size(), corrupt);

// Get indices
std::vector<def_table_t> indices;
ensure(stream.read(indices, header.entries_num));
PSF_CHECK(stream.read<true>(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<true>(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);
Expand All @@ -156,7 +173,7 @@ namespace psf
{
// Integer data
le_t<u32> value;
ensure(stream.read(value));
PSF_CHECK(stream.read(value), corrupt);

result.emplace(std::piecewise_construct,
std::forward_as_tuple(std::move(key)),
Expand All @@ -166,7 +183,7 @@ namespace psf
{
// String/array data
std::string value;
ensure(stream.read(value, indices[i].param_len));
PSF_CHECK(stream.read<true>(value, indices[i].param_len), corrupt);

if (indices[i].param_fmt == format::string)
{
Expand All @@ -185,7 +202,8 @@ namespace psf
}
}

return result;
#undef PSF_CHECK
return pair;
}

std::vector<u8> save_object(const psf::registry& psf, std::vector<u8>&& init)
Expand Down
10 changes: 9 additions & 1 deletion rpcs3/Loader/PSF.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ namespace psf
integer = 0x0404,
};

enum class error
{
ok,
stream,
not_psf,
corrupt,
};

class entry final
{
format m_type;
Expand Down Expand Up @@ -50,7 +58,7 @@ namespace psf
using registry = std::map<std::string, entry>;

// Load PSF registry from SFO binary format
registry load_object(const fs::file&);
std::pair<registry, error> load_object(const fs::file&);

// Convert PSF registry to SFO binary format
std::vector<u8> save_object(const registry&, std::vector<u8>&& init = std::vector<u8>{});
Expand Down
11 changes: 5 additions & 6 deletions rpcs3/rpcs3qt/game_list_frame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -550,13 +550,8 @@ 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)
{
return;
}

const auto psf = psf::load_object(sfo_file);
const auto psf = psf::load_object(fs::file(sfo_dir + "/PARAM.SFO")).first;

GameInfo game;
game.path = dir;
Expand All @@ -572,6 +567,10 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
game.bootable = psf::get_integer(psf, "BOOTABLE", 0);
game.attr = psf::get_integer(psf, "ATTRIBUTE", 0);

if (game.serial.empty())
{
return;
}
if (m_show_custom_icons)
{
game.icon_path = fs::get_config_dir() + "/Icons/game_icons/" + game.serial + "/ICON0.PNG";
Expand Down
4 changes: 2 additions & 2 deletions rpcs3/rpcs3qt/save_manager_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_object(fs::file(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;
}

Expand Down

0 comments on commit 9c58e35

Please sign in to comment.