mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-08 07:51:28 +12:00
Loader/cellGame: Do not crash on invalid PSF files
This commit is contained in:
parent
10bbb7fa1f
commit
a8d5a8734a
5 changed files with 88 additions and 40 deletions
|
@ -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;
|
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)
|
if (!new_data)
|
||||||
{
|
{
|
||||||
|
@ -586,7 +586,7 @@ error_code cellGameDataCheck(u32 type, vm::cptr<char> dirName, vm::ptr<CellGameC
|
||||||
return CELL_GAME_ERROR_BUSY;
|
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") != [&]()
|
if (psf::get_string(sfo, "CATEGORY") != [&]()
|
||||||
{
|
{
|
||||||
|
@ -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();
|
init.cancel();
|
||||||
return CELL_GAME_ERROR_BROKEN;
|
return CELL_GAME_ERROR_BROKEN;
|
||||||
|
@ -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 game_dir = dirName.get_ptr();
|
||||||
const std::string dir = "/dev_hdd0/game/"s + game_dir;
|
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)
|
if (!new_data)
|
||||||
{
|
{
|
||||||
|
@ -961,9 +961,9 @@ error_code cellGameDeleteGameData(vm::cptr<char> dirName)
|
||||||
return CELL_GAME_ERROR_NOTSUPPORTED;
|
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;
|
return CELL_GAME_ERROR_NOTSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
namespace psf
|
||||||
{
|
{
|
||||||
struct header_t
|
struct header_t
|
||||||
|
@ -103,51 +120,52 @@ namespace psf
|
||||||
fmt::throw_exception("Invalid format (0x%x)", m_type);
|
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)
|
load_result_t pair{};
|
||||||
if (!stream || !stream.size())
|
auto& [result, errc] = pair;
|
||||||
{
|
|
||||||
return result;
|
PSF_CHECK(stream, stream);
|
||||||
}
|
|
||||||
|
|
||||||
stream.seek(0);
|
stream.seek(0);
|
||||||
|
|
||||||
// Get header
|
// Get header
|
||||||
header_t header;
|
header_t header;
|
||||||
ensure(stream.read(header));
|
PSF_CHECK(stream.read(header), not_psf);
|
||||||
|
|
||||||
// Check magic and version
|
// Check magic and version
|
||||||
ensure(header.magic == "\0PSF"_u32);
|
PSF_CHECK(header.magic == "\0PSF"_u32, not_psf);
|
||||||
ensure(header.version == 0x101u);
|
PSF_CHECK(header.version == 0x101u, not_psf);
|
||||||
ensure(sizeof(header_t) + header.entries_num * sizeof(def_table_t) <= header.off_key_table);
|
PSF_CHECK(header.off_key_table >= sizeof(header_t), corrupt);
|
||||||
ensure(header.off_key_table <= header.off_data_table);
|
PSF_CHECK(header.off_key_table <= header.off_data_table, corrupt);
|
||||||
ensure(header.off_data_table <= stream.size());
|
PSF_CHECK(header.off_data_table <= stream.size(), corrupt);
|
||||||
|
|
||||||
// Get indices
|
// Get indices
|
||||||
std::vector<def_table_t> 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
|
// Get keys
|
||||||
std::string keys;
|
std::string keys;
|
||||||
ensure(stream.seek(header.off_key_table) == header.off_key_table);
|
PSF_CHECK(stream.seek(header.off_key_table) == header.off_key_table, corrupt);
|
||||||
ensure(stream.read(keys, header.off_data_table - header.off_key_table));
|
PSF_CHECK(stream.read<true>(keys, header.off_data_table - header.off_key_table), corrupt);
|
||||||
|
|
||||||
// Load entries
|
// Load entries
|
||||||
for (u32 i = 0; i < header.entries_num; ++i)
|
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)
|
// Get key name (null-terminated string)
|
||||||
std::string key(keys.data() + indices[i].key_off);
|
std::string key(keys.data() + indices[i].key_off);
|
||||||
|
|
||||||
// Check entry
|
// Check entry
|
||||||
ensure(result.count(key) == 0);
|
PSF_CHECK(result.count(key) == 0, corrupt);
|
||||||
ensure(indices[i].param_len <= indices[i].param_max);
|
PSF_CHECK(indices[i].param_len <= indices[i].param_max, corrupt);
|
||||||
ensure(indices[i].data_off < stream.size() - header.off_data_table);
|
PSF_CHECK(indices[i].data_off < stream.size() - header.off_data_table, corrupt);
|
||||||
ensure(indices[i].param_max < stream.size() - indices[i].data_off);
|
PSF_CHECK(indices[i].param_max < stream.size() - indices[i].data_off, corrupt);
|
||||||
|
|
||||||
// Seek data pointer
|
// Seek data pointer
|
||||||
stream.seek(header.off_data_table + indices[i].data_off);
|
stream.seek(header.off_data_table + indices[i].data_off);
|
||||||
|
@ -156,7 +174,7 @@ namespace psf
|
||||||
{
|
{
|
||||||
// Integer data
|
// Integer data
|
||||||
le_t<u32> value;
|
le_t<u32> value;
|
||||||
ensure(stream.read(value));
|
PSF_CHECK(stream.read(value), corrupt);
|
||||||
|
|
||||||
result.emplace(std::piecewise_construct,
|
result.emplace(std::piecewise_construct,
|
||||||
std::forward_as_tuple(std::move(key)),
|
std::forward_as_tuple(std::move(key)),
|
||||||
|
@ -166,7 +184,7 @@ namespace psf
|
||||||
{
|
{
|
||||||
// String/array data
|
// String/array data
|
||||||
std::string value;
|
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)
|
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<u8> save_object(const psf::registry& psf, std::vector<u8>&& init)
|
std::vector<u8> save_object(const psf::registry& psf, std::vector<u8>&& init)
|
||||||
|
|
|
@ -19,6 +19,14 @@ namespace psf
|
||||||
integer = 0x0404,
|
integer = 0x0404,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class error
|
||||||
|
{
|
||||||
|
ok,
|
||||||
|
stream,
|
||||||
|
not_psf,
|
||||||
|
corrupt,
|
||||||
|
};
|
||||||
|
|
||||||
class entry final
|
class entry final
|
||||||
{
|
{
|
||||||
format m_type{};
|
format m_type{};
|
||||||
|
@ -49,8 +57,21 @@ namespace psf
|
||||||
// Define PSF registry as a sorted map of entries:
|
// Define PSF registry as a sorted map of entries:
|
||||||
using registry = std::map<std::string, entry>;
|
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
|
// 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
|
// Convert PSF registry to SFO binary format
|
||||||
std::vector<u8> save_object(const registry&, std::vector<u8>&& init = std::vector<u8>{});
|
std::vector<u8> save_object(const registry&, std::vector<u8>&& init = std::vector<u8>{});
|
||||||
|
|
|
@ -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 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto psf = psf::load_object(sfo_file);
|
|
||||||
|
|
||||||
GameInfo game;
|
GameInfo game;
|
||||||
game.path = dir;
|
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.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.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));
|
game.version = std::string(psf::get_string(psf, "VERSION", cat_unknown_localized));
|
||||||
|
|
|
@ -55,11 +55,11 @@ namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
// PSF parameters
|
// 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())
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue