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

cellGameDataCheckCreate: Truncate excess of characters in TITLE_ID #13348

Merged
merged 1 commit into from Feb 7, 2023
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
14 changes: 11 additions & 3 deletions rpcs3/Emu/Cell/Modules/cellGame.cpp
Expand Up @@ -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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the size parameter seems completely unused

// 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);
Expand Down Expand Up @@ -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);
Expand All @@ -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());
Expand Down Expand Up @@ -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) },
};
Expand Down
5 changes: 5 additions & 0 deletions rpcs3/Emu/Cell/Modules/cellGame.h
Expand Up @@ -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];
Expand Down
8 changes: 8 additions & 0 deletions rpcs3/Emu/System.cpp
Expand Up @@ -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)
Expand Down
93 changes: 73 additions & 20 deletions rpcs3/Loader/PSF.cpp
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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, \
Expand Down Expand Up @@ -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;
}
Expand All @@ -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:
Expand Down Expand Up @@ -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))
{
Expand All @@ -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
{
Expand Down Expand Up @@ -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;
}
}
26 changes: 21 additions & 5 deletions rpcs3/Loader/PSF.h
Expand Up @@ -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;

Expand All @@ -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:
Expand Down Expand Up @@ -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)
{
Expand All @@ -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, 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)
{
return {format::string, max_size, value};
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
Expand Down