Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loader/cellGame: Do not crash on invalid PSF files #10041

Merged
merged 2 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 8 additions & 8 deletions rpcs3/Emu/Cell/Modules/cellGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,9 @@ 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")));
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)
{
Expand Down Expand Up @@ -586,7 +586,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_error] = psf::load(vfs::get(dir + "/PARAM.SFO"));

if (psf::get_string(sfo, "CATEGORY") != [&]()
{
Expand All @@ -599,7 +599,7 @@ error_code cellGameDataCheck(u32 type, vm::cptr<char> dirName, vm::ptr<CellGameC
}
}())
{
if (fs::is_file(vfs::get(dir + "/PARAM.SFO")))
if (psf_error != psf::error::stream)
{
init.cancel();
return CELL_GAME_ERROR_BROKEN;
Expand Down Expand Up @@ -714,9 +714,9 @@ 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")));
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)
{
Expand Down Expand Up @@ -961,9 +961,9 @@ error_code cellGameDeleteGameData(vm::cptr<char> 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;
}
Expand Down
76 changes: 50 additions & 26 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 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
Expand All @@ -42,7 +59,7 @@ namespace psf
};


entry::entry(format type, u32 max_size, const std::string& value)
entry::entry(format type, u32 max_size, std::string_view value)
: m_type(type)
, m_max_size(max_size)
, m_value_string(value)
Expand Down Expand Up @@ -74,7 +91,7 @@ namespace psf
return m_value_integer;
}

entry& entry::operator =(const std::string& value)
entry& entry::operator =(std::string_view value)
{
ensure(m_type == format::string || m_type == format::array);
m_value_string = value;
Expand Down Expand Up @@ -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<bool>(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<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 +174,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 +184,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 +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<u8> save_object(const psf::registry& psf, std::vector<u8>&& init)
Expand Down
31 changes: 26 additions & 5 deletions 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 All @@ -28,7 +36,7 @@ namespace psf

public:
// Construct string entry, assign the value
entry(format type, u32 max_size, const std::string& value = {});
entry(format type, u32 max_size, std::string_view value);

// Construct integer entry, assign the value
entry(u32 value);
Expand All @@ -38,7 +46,7 @@ namespace psf
const std::string& as_string() const;
u32 as_integer() const;

entry& operator =(const std::string& value);
entry& operator =(std::string_view value);
entry& operator =(u32 value);

format type() const { return m_type; }
Expand All @@ -49,8 +57,21 @@ namespace psf
// Define PSF registry as a sorted map of entries:
using registry = std::map<std::string, entry>;

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<u8> save_object(const registry&, std::vector<u8>&& init = std::vector<u8>{});
Expand Down Expand Up @@ -79,12 +100,12 @@ namespace psf
// Make string entry
inline entry string(u32 max_size, std::string_view value)
{
return {format::string, max_size, std::string(value)};
return {format::string, max_size, value};
}

// Make array entry
inline entry array(u32 max_size, std::string_view value)
{
return {format::array, max_size, std::string(value)};
return {format::array, max_size, value};
}
}
13 changes: 8 additions & 5 deletions rpcs3/rpcs3qt/game_list_frame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
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(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