cellGame: Truncate excess of characters in TITLE_ID

In cellGameDataCheckCreate
This commit is contained in:
Eladash 2023-02-04 11:46:34 +02:00 committed by Ivan
parent 5a63271f0e
commit 27cad422b9
5 changed files with 118 additions and 28 deletions

View File

@ -466,7 +466,7 @@ error_code cellHddGameCheck(ppu_thread& ppu, u32 version, vm::cptr<char> dirName
// psf::assign(sfo, "CATEGORY", psf::string(3, "HG"));
// }
// psf::assign(sfo, "TITLE_ID", psf::string(CELL_GAME_SYSP_TITLEID_SIZE, setParam->titleId));
// psf::assign(sfo, "TITLE_ID", psf::string(TITLEID_SFO_ENTRY_SIZE, setParam->titleId));
// psf::assign(sfo, "TITLE", psf::string(CELL_GAME_SYSP_TITLE_SIZE, setParam->title));
// psf::assign(sfo, "VERSION", psf::string(CELL_GAME_SYSP_VERSION_SIZE, setParam->dataVersion));
// psf::assign(sfo, "PARENTAL_LEVEL", +setParam->parentalLevel);
@ -990,7 +990,7 @@ error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr<char>
psf::assign(sfo, "CATEGORY", psf::string(3, "GD"));
}
psf::assign(sfo, "TITLE_ID", psf::string(CELL_GAME_SYSP_TITLEID_SIZE, setParam->titleId));
psf::assign(sfo, "TITLE_ID", psf::string(TITLEID_SFO_ENTRY_SIZE, setParam->titleId, true));
psf::assign(sfo, "TITLE", psf::string(CELL_GAME_SYSP_TITLE_SIZE, setParam->title));
psf::assign(sfo, "VERSION", psf::string(CELL_GAME_SYSP_VERSION_SIZE, setParam->dataVersion));
psf::assign(sfo, "PARENTAL_LEVEL", +setParam->parentalLevel);
@ -1005,6 +1005,14 @@ error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr<char>
psf::assign(sfo, fmt::format("TITLE_%02d", i), psf::string(CELL_GAME_SYSP_TITLE_SIZE, setParam->titleLang[i]));
}
if (!psf::check_registry(sfo))
{
// This results in CELL_OK, broken SFO and CELL_GAMEDATA_ERROR_BROKEN on the next load
// Avoid creation for now
cellGame.error("Broken SFO paramters: %s", sfo);
return CELL_OK;
}
fs::pending_file temp(vfs::get(dir + "/PARAM.SFO"));
temp.file.write(psf::save_object(sfo));
ensure(temp.commit());
@ -1125,7 +1133,7 @@ error_code cellGameCreateGameData(vm::ptr<CellGameSetInitParams> init, vm::ptr<c
perm.sfo =
{
{ "CATEGORY", psf::string(3, "GD") },
{ "TITLE_ID", psf::string(CELL_GAME_SYSP_TITLEID_SIZE, init->titleId) },
{ "TITLE_ID", psf::string(TITLEID_SFO_ENTRY_SIZE, init->titleId) },
{ "TITLE", psf::string(CELL_GAME_SYSP_TITLE_SIZE, init->title) },
{ "VERSION", psf::string(CELL_GAME_SYSP_VERSION_SIZE, init->version) },
};

View File

@ -216,6 +216,11 @@ enum // old consts
CELL_DISCGAME_SYSP_TITLEID_SIZE=10,
};
enum
{
TITLEID_SFO_ENTRY_SIZE = 16, // This is the true length on PS3 (TODO: Fix in more places)
};
struct CellGameDataSystemFileParam
{
char title[CELL_GAMEDATA_SYSP_TITLE_SIZE];

View File

@ -1036,6 +1036,14 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
// Try to boot a game through game ID only
m_title_id = m_path.substr(("%RPCS3_GAMEID%:"sv).size());
m_title_id = m_title_id.substr(0, m_title_id.find_first_of(fs::delim));
if (m_title_id.size() < 3 && m_title_id.find_first_not_of('.') == umax)
{
// Do not allow if TITLE_ID result in path redirection
sys_log.fatal("Game directory not found using GAMEID token. ('%s')", m_title_id);
return game_boot_result::invalid_file_or_folder;
}
std::string tail = m_path.substr(("%RPCS3_GAMEID%:"sv).size() + m_title_id.size());
if (tail.find_first_not_of(fs::delim) == umax)

View File

@ -100,16 +100,21 @@ namespace psf
};
entry::entry(format type, u32 max_size, std::string_view value)
entry::entry(format type, u32 max_size, std::string_view value, bool allow_truncate) noexcept
: m_type(type)
, m_max_size(max_size)
, m_value_string(value)
{
ensure(type == format::string || type == format::array);
ensure(max_size);
ensure(max_size > (type == format::string ? 1 : 0));
if (allow_truncate && value.size() > max(false))
{
m_value_string.resize(max(false));
}
}
entry::entry(u32 value)
entry::entry(u32 value) noexcept
: m_type(format::integer)
, m_max_size(sizeof(u32))
, m_value_integer(value)
@ -148,7 +153,7 @@ namespace psf
{
case format::string:
case format::array:
return std::min(m_max_size, ::narrow<u32>(m_value_string.size() + (m_type == format::string)));
return std::min(m_max_size, ::narrow<u32>(m_value_string.size() + (m_type == format::string ? 1 : 0)));
case format::integer:
return sizeof(u32);
@ -157,6 +162,22 @@ namespace psf
fmt::throw_exception("Invalid format (0x%x)", m_type);
}
bool entry::is_valid() const
{
switch (m_type)
{
case format::string:
case format::array:
return m_value_string.size() <= this->max(false);
case format::integer:
return true;
default: break;
}
fmt::throw_exception("Invalid format (0x%x)", m_type);
}
load_result_t load(const fs::file& stream, std::string_view filename)
{
#define PSF_CHECK(cond, err) if (!static_cast<bool>(cond)) { if (error::err != error::stream) psf_log.error("Error loading PSF '%s': %s%s", filename, error::err, \
@ -251,14 +272,6 @@ namespace psf
PSF_CHECK(false, corrupt);
}
const auto tid = get_string(pair.sfo, "TITLE_ID", "");
if (std::find_if(tid.begin(), tid.end(), [](char ch){ return !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')); }) != tid.end())
{
psf_log.error("Invalid title ID ('%s')", tid);
PSF_CHECK(false, corrupt);
}
#undef PSF_CHECK
return pair;
}
@ -283,7 +296,7 @@ namespace psf
index.key_off = ::narrow<u32>(key_offset);
index.param_fmt = entry.second.type();
index.param_len = entry.second.size();
index.param_max = entry.second.max();
index.param_max = entry.second.max(true);
index.data_off = ::narrow<u32>(data_offset);
// Update offsets:
@ -322,7 +335,7 @@ namespace psf
for (const auto& entry : psf)
{
const auto fmt = entry.second.type();
const u32 max = entry.second.max();
const u32 max = entry.second.max(true);
if (fmt == format::integer && max == sizeof(u32))
{
@ -331,17 +344,17 @@ namespace psf
}
else if (fmt == format::string || fmt == format::array)
{
const std::string& value = entry.second.as_string();
const usz size = std::min<usz>(max, value.size());
std::string_view value = entry.second.as_string();
if (value.size() + (fmt == format::string) > max)
if (!entry.second.is_valid())
{
// TODO: check real limitations of PSF format
psf_log.error("Entry value shrinkage (key='%s', value='%s', size=0x%zx, max=0x%x)", entry.first, value, size, max);
psf_log.error("Entry value shrinkage (key='%s', value='%s', size=0x%zx, max=0x%x)", entry.first, value, value.size(), max);
value = value.substr(0, entry.second.max(false));
}
stream.write(value);
stream.trunc(stream.seek(max - size, fs::seek_cur)); // Skip up to max_size
stream.write(value.data(), value.size());
stream.trunc(stream.seek(max - value.size(), fs::seek_cur)); // Skip up to max_size
}
else
{
@ -375,4 +388,44 @@ namespace psf
return found->second.as_integer();
}
bool check_registry(const registry& psf, std::function<bool(bool ok, const std::string& key, const entry& value)> validate, u32 line, u32 col, const char* file, const char* func)
{
bool psf_ok = true;
for (const auto& [key, value] : psf)
{
bool entry_ok = value.is_valid();
if (validate)
{
// Validate against a custom condition as well (forward error)
if (!validate(entry_ok, key, value))
{
entry_ok = false;
}
}
if (!entry_ok)
{
if (value.type() == format::string)
{
psf_log.error("Entry '%s' is invalid: string='%s'.%s", key, value.as_string(), src_loc{line , col, file, func});
}
else
{
// TODO: Better logging of other types
psf_log.error("Entry %s is invalid.%s", key, value.as_string(), src_loc{line , col, file, func});
}
}
if (!entry_ok)
{
// Do not break, run over all entries in order to report all errors
psf_ok = false;
}
}
return psf_ok;
}
}

View File

@ -55,10 +55,10 @@ namespace psf
public:
// Construct string entry, assign the value
entry(format type, u32 max_size, std::string_view value);
entry(format type, u32 max_size, std::string_view value, bool allow_truncate = false) noexcept;
// Construct integer entry, assign the value
entry(u32 value);
entry(u32 value) noexcept;
~entry() = default;
@ -69,8 +69,9 @@ namespace psf
entry& operator =(u32 value);
format type() const { return m_type; }
u32 max() const { return m_max_size; }
u32 max(bool with_nts) const { return m_max_size - (!with_nts && m_type == format::string ? 1 : 0); }
u32 size() const;
bool is_valid() const;
};
// Define PSF registry as a sorted map of entries:
@ -102,6 +103,12 @@ namespace psf
// Get integer value or default value
u32 get_integer(const registry& psf, std::string_view key, u32 def = 0);
bool check_registry(const registry& psf, std::function<bool(bool ok, const std::string& key, const entry& value)> validate = {},
u32 line = __builtin_LINE(),
u32 col = __builtin_COLUMN(),
const char* file = __builtin_FILE(),
const char* func = __builtin_FUNCTION());
// Assign new entry
inline void assign(registry& psf, std::string_view key, entry&& _entry)
{
@ -118,9 +125,18 @@ namespace psf
}
// Make string entry
inline entry string(u32 max_size, std::string_view value)
inline entry string(u32 max_size, std::string_view value, bool allow_truncate = false)
{
return {format::string, max_size, value};
return {format::string, max_size, value, allow_truncate};
}
// Make string entry (from char[N])
template <usz CharN>
inline entry string(u32 max_size, char (&value_array)[CharN], bool allow_truncate = false)
{
std::string_view value{value_array, CharN};
value = value.substr(0, std::min<usz>(value.find_first_of('\0'), value.size()));
return string(CharN, value, allow_truncate);
}
// Make array entry