#include "loader/systemContent.h" #include "common/assert.h" #include "common/common.h" #include "common/file.h" #include "common/logging/log.h" #include "common/singleton.h" #include "common/threads.h" #include #include #include #include #include #include #include #include #include namespace Loader { class Psf { public: Psf() = default; virtual ~Psf(); KYTY_CLASS_NO_COPY(Psf); void Open(const std::filesystem::path& file_name); void OpenChunkDefs(const std::filesystem::path& file_name); void Close(); bool IsValid(); bool GetParamInt(const char* name, int32_t* value); bool GetParamString(const char* name, std::string* value); bool GetParamString(const char* name, char* value, size_t value_size); uint64_t GetFlexibleMemorySize(); void DbgPrint(); private: enum class ParamType { Text, Int, }; struct Param { std::string name; ParamType type = ParamType::Text; std::string string_value; std::string string_utf8; int32_t int_value = 0; }; void OpenJson(const std::filesystem::path& file_name); void AddParamString(const std::string& name, const std::string& value); void AddParamInt(const std::string& name, int32_t value); Param* FindParam(const char* name); Common::Mutex m_mutex; bool m_opened = false; std::vector m_params; uint64_t m_flexible_memory_size = 0; }; class PlayGo { public: PlayGo() = default; virtual ~PlayGo(); KYTY_CLASS_NO_COPY(PlayGo); void Open(const std::filesystem::path& file_name); void OpenChunkDefs(const std::filesystem::path& file_name); void OpenChunkManifest(const std::filesystem::path& file_name); void Close(); bool IsValid(); bool GetChunksNum(uint32_t* num); void DbgPrint(); private: Common::File m_f; Common::Mutex m_mutex; bool m_opened = false; uint16_t m_chunks_num = 0; }; struct SystemContent { std::filesystem::path psf_path; Psf psf; std::filesystem::path playgo_path; PlayGo playgo; std::filesystem::path icon_path; }; Psf::~Psf() { Close(); } void Psf::Open(const std::filesystem::path& file_name) { Common::LockGuard lock(m_mutex); Close(); OpenJson(file_name); } void Psf::OpenJson(const std::filesystem::path& file_name) { Common::File f; f.Open(file_name, Common::File::Mode::Read); if (f.IsInvalid()) { LOGF("Can't open %s\n", Common::PathToString(file_name).c_str()); return; } auto buf = f.ReadWholeBuffer(); if (buf.empty()) { LOGF("invalid json file: %s\n", Common::PathToString(file_name).c_str()); f.Close(); return; } std::string default_language; std::vector localized_languages; std::vector localized_titles; const auto* json_begin = reinterpret_cast(buf.data()); const auto* json_end = json_begin + buf.size(); auto param_json = nlohmann::ordered_json::parse(json_begin, json_end, nullptr, false); if (param_json.is_discarded() || !param_json.is_object()) { LOGF("invalid json file: %s\n", Common::PathToString(file_name).c_str()); f.Close(); return; } auto add_string_param = [&](const char* json_key, const char* param_key) { auto it = param_json.find(json_key); if (it != param_json.end() && it->is_string()) { AddParamString(param_key, it->get_ref()); } }; auto add_int_param = [&](const char* json_key, const char* param_key) { auto it = param_json.find(json_key); if (it == param_json.end() || !it->is_number_integer()) { return; } int64_t value = 0; if (it->is_number_unsigned()) { value = static_cast(it->get()); } else { value = it->get(); } AddParamInt(param_key, static_cast(value)); }; add_string_param("titleId", "TITLE_ID"); add_string_param("contentId", "CONTENT_ID"); add_string_param("contentVersion", "APP_VER"); add_string_param("appVersion", "APP_VER"); add_int_param("attribute3", "ATTRIBUTE3"); add_int_param("userDefinedParam1", "USER_DEFINED_PARAM_1"); add_int_param("userDefinedParam2", "USER_DEFINED_PARAM_2"); add_int_param("userDefinedParam3", "USER_DEFINED_PARAM_3"); add_int_param("userDefinedParam4", "USER_DEFINED_PARAM_4"); if (auto kernel = param_json.find("kernel"); kernel != param_json.end() && kernel->is_object()) { if (auto it = kernel->find("flexibleMemorySize"); it != kernel->end()) { m_flexible_memory_size = it->get(); } } auto localized = param_json.find("localizedParameters"); if (localized != param_json.end() && localized->is_object()) { auto default_it = localized->find("defaultLanguage"); if (default_it != localized->end() && default_it->is_string()) { default_language = default_it->get_ref(); } for (auto it = localized->begin(); it != localized->end(); ++it) { if (!it->is_object()) { continue; } auto title_it = it->find("titleName"); if (title_it != it->end() && title_it->is_string()) { localized_languages.push_back(it.key()); localized_titles.push_back(title_it->get_ref()); } } } std::string title; for (size_t i = 0; i < localized_titles.size(); i++) { if (!default_language.empty() && localized_languages[i] == default_language) { title = localized_titles[i]; break; } } if (title.empty()) { for (size_t i = 0; i < localized_titles.size(); i++) { if (localized_languages[i] == "en-US") { title = localized_titles[i]; break; } } } if (title.empty() && !localized_titles.empty()) { title = localized_titles[0]; } if (!title.empty()) { AddParamString("TITLE", title); } f.Close(); m_opened = true; } void Psf::Close() { Common::LockGuard lock(m_mutex); m_params.clear(); m_flexible_memory_size = 0; m_opened = false; } bool Psf::IsValid() { Common::LockGuard lock(m_mutex); return m_opened; } void Psf::DbgPrint() { Common::LockGuard lock(m_mutex); if (!m_opened) { return; } for (const auto& param: m_params) { LOGF("%s, ", param.name.c_str()); switch (param.type) { case ParamType::Text: { auto str = Common::ReplaceChar(param.string_value, '\n', ','); LOGF("string = %s\n", str.c_str()); break; } case ParamType::Int: LOGF("int = 0x%08" PRIx32 "\n", static_cast(param.int_value)); break; default: EXIT("unknown type\n"); } } } Psf::Param* Psf::FindParam(const char* name) { for (auto& param: m_params) { if (param.name == name) { return ¶m; } } return nullptr; } void Psf::AddParamString(const std::string& name, const std::string& value) { auto* param = FindParam(name.c_str()); if (param == nullptr) { m_params.push_back({}); param = &m_params.back(); param->name = name; } param->type = ParamType::Text; param->string_utf8 = value; param->string_value = value; } void Psf::AddParamInt(const std::string& name, int32_t value) { auto* param = FindParam(name.c_str()); if (param == nullptr) { m_params.push_back({}); param = &m_params.back(); param->name = name; } param->type = ParamType::Int; param->int_value = value; } bool Psf::GetParamInt(const char* name, int32_t* value) { Common::LockGuard lock(m_mutex); if (!m_opened) { return false; } if (auto* param = FindParam(name); param != nullptr && param->type == ParamType::Int) { *value = param->int_value; return true; } return false; } bool Psf::GetParamString(const char* name, std::string* value) { Common::LockGuard lock(m_mutex); if (!m_opened) { return false; } if (auto* param = FindParam(name); param != nullptr && param->type == ParamType::Text) { *value = param->string_value; return true; } return false; } bool Psf::GetParamString(const char* name, char* value, size_t value_size) { Common::LockGuard lock(m_mutex); if (!m_opened) { return false; } if (auto* param = FindParam(name); param != nullptr && param->type == ParamType::Text && param->string_utf8.size() + 1 <= value_size) { std::memcpy(value, param->string_utf8.c_str(), param->string_utf8.size() + 1); return true; } return false; } uint64_t Psf::GetFlexibleMemorySize() { Common::LockGuard lock(m_mutex); return m_flexible_memory_size; } PlayGo::~PlayGo() { Close(); } void PlayGo::Open(const std::filesystem::path& file_name) { Common::LockGuard lock(m_mutex); Close(); m_f.Open(file_name, Common::File::Mode::Read); if (m_f.IsInvalid()) { LOGF("Can't open %s\n", Common::PathToString(file_name).c_str()); return; } uint32_t magic1 = 0; m_f.Read(&magic1, 4); if (magic1 != 0x6f676c70) { LOGF("invalid file: magic1 = %08" PRIx32 "\n", magic1); return; } m_f.Seek(10); m_f.Read(&m_chunks_num, 2); m_opened = true; } static bool ParseXmlUintAttr(std::string_view tag, std::string_view attr, uint32_t& value) { const auto attr_pos = tag.find(attr); if (attr_pos == std::string_view::npos) { return false; } auto pos = attr_pos + attr.size(); while (pos < tag.size() && (tag[pos] == ' ' || tag[pos] == '\t')) { pos++; } if (pos >= tag.size() || tag[pos] != '=') { return false; } pos++; while (pos < tag.size() && (tag[pos] == ' ' || tag[pos] == '\t')) { pos++; } if (pos >= tag.size() || (tag[pos] != '"' && tag[pos] != '\'')) { return false; } const char quote = tag[pos++]; const auto start = pos; while (pos < tag.size() && tag[pos] >= '0' && tag[pos] <= '9') { pos++; } if (start == pos || pos >= tag.size() || tag[pos] != quote) { return false; } uint32_t parsed = 0; const auto [ptr, ec] = std::from_chars(tag.data() + start, tag.data() + pos, parsed); if (ec != std::errc {} || ptr != tag.data() + pos) { return false; } value = parsed; return true; } void PlayGo::OpenChunkDefs(const std::filesystem::path& file_name) { Common::LockGuard lock(m_mutex); if (m_opened) { return; } Common::File f; f.Open(file_name, Common::File::Mode::Read); if (f.IsInvalid()) { LOGF("Can't open %s\n", Common::PathToString(file_name).c_str()); return; } auto buf = f.ReadWholeBuffer(); f.Close(); if (buf.empty()) { LOGF("invalid file: %s\n", Common::PathToString(file_name).c_str()); return; } const std::string_view xml(reinterpret_cast(buf.data()), buf.size()); uint32_t max_chunk_id = 0; bool found = false; if (const auto chunks_pos = xml.find("', chunks_pos); if (tag_end != std::string_view::npos) { uint32_t default_chunk = 0; if (ParseXmlUintAttr(xml.substr(chunks_pos, tag_end - chunks_pos), "default_chunk", default_chunk)) { max_chunk_id = std::max(max_chunk_id, default_chunk); found = true; } } } size_t pos = 0; while ((pos = xml.find("= xml.size() || !(xml[name_end] == ' ' || xml[name_end] == '\t' || xml[name_end] == '\r' || xml[name_end] == '\n')) { pos = name_end; continue; } const auto tag_end = xml.find('>', name_end); if (tag_end == std::string_view::npos) { break; } uint32_t chunk_id = 0; if (ParseXmlUintAttr(xml.substr(pos, tag_end - pos), "id", chunk_id)) { max_chunk_id = std::max(max_chunk_id, chunk_id); found = true; } pos = tag_end + 1; } if (!found || max_chunk_id >= 1000) { LOGF("invalid playgo chunk definitions: max_chunk_id=%" PRIu32 ", path=%s\n", max_chunk_id, Common::PathToString(file_name).c_str()); return; } m_chunks_num = static_cast(max_chunk_id + 1); m_opened = true; } void PlayGo::OpenChunkManifest(const std::filesystem::path& file_name) { Common::LockGuard lock(m_mutex); if (m_opened) { return; } Common::File f; f.Open(file_name, Common::File::Mode::Read); if (f.IsInvalid()) { LOGF("Can't open %s\n", Common::PathToString(file_name).c_str()); return; } auto buf = f.ReadWholeBuffer(); f.Close(); constexpr uint8_t id_field[] = {0x08, 'i', 'd', 0x00}; bool chunks[1000] {}; uint32_t max_chunk_id = 0; bool found = false; const auto* data = buf.data(); for (size_t pos = 0; pos + sizeof(id_field) + sizeof(uint32_t) <= buf.size(); pos++) { if (std::memcmp(data + pos, id_field, sizeof(id_field)) != 0) { continue; } const auto* value = data + pos + sizeof(id_field); const auto chunk_id = static_cast(value[0]) | (static_cast(value[1]) << 8u) | (static_cast(value[2]) << 16u) | (static_cast(value[3]) << 24u); if (chunk_id < 1000) { chunks[chunk_id] = true; max_chunk_id = std::max(max_chunk_id, chunk_id); found = true; } } if (!found || !std::all_of(chunks, chunks + max_chunk_id + 1, [](bool chunk) { return chunk; })) { LOGF("invalid chunk manifest: path=%s\n", Common::PathToString(file_name).c_str()); return; } m_chunks_num = static_cast(max_chunk_id + 1); m_opened = true; LOGF("PlayGo: chunks num = %" PRIu16 " (from %s)\n", m_chunks_num, Common::PathToString(file_name).c_str()); } void PlayGo::Close() { Common::LockGuard lock(m_mutex); if (!m_f.IsInvalid()) { m_f.Close(); } m_opened = false; m_chunks_num = 0; } bool PlayGo::IsValid() { Common::LockGuard lock(m_mutex); return m_opened; } bool PlayGo::GetChunksNum(uint32_t* num) { Common::LockGuard lock(m_mutex); if (m_opened) { *num = m_chunks_num; return true; } return false; } void PlayGo::DbgPrint() { Common::LockGuard lock(m_mutex); if (!m_opened) { return; } LOGF("PlayGo: chunks num = %" PRIu16 "\n", m_chunks_num); } void SystemContentLoadParamSfo(const std::filesystem::path& file_name) { auto* sc = Common::Singleton::Instance(); if (!Common::File::IsFileExisting(file_name)) { EXIT("Can't find file: %s\n", Common::PathToString(file_name).c_str()); } sc->psf.Open(file_name); if (!sc->psf.IsValid()) { EXIT("invalid file: %s\n", Common::PathToString(file_name).c_str()); } sc->psf_path = file_name; sc->psf.DbgPrint(); sc->icon_path = file_name.parent_path() / "icon0.png"; if (!Common::File::IsFileExisting(sc->icon_path)) { sc->icon_path.clear(); } sc->playgo_path = file_name.parent_path() / "playgo-chunk.dat"; sc->playgo.Open(sc->playgo_path); if (!sc->playgo.IsValid()) { sc->playgo_path = file_name.parent_path().parent_path() / "playgo-chunkdefs.xml"; sc->playgo.OpenChunkDefs(sc->playgo_path); } if (!sc->playgo.IsValid()) { sc->playgo_path = file_name.parent_path().parent_path() / "Data" / "chunkmanifest"; sc->playgo.OpenChunkManifest(sc->playgo_path); } if (sc->playgo.IsValid()) { sc->playgo.DbgPrint(); } } bool SystemContentParamSfoGetInt(const char* name, int32_t* value) { if (name == nullptr || value == nullptr) { return false; } auto* sc = Common::Singleton::Instance(); return sc->psf.GetParamInt(name, value); } bool SystemContentParamSfoGetString(const char* name, std::string* value) { if (name == nullptr || value == nullptr) { return false; } auto* sc = Common::Singleton::Instance(); return sc->psf.GetParamString(name, value); } bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_size) { if (name == nullptr || value == nullptr) { return false; } auto* sc = Common::Singleton::Instance(); return sc->psf.GetParamString(name, value, value_size); } uint64_t SystemContentGetFlexibleMemorySize() { auto* sc = Common::Singleton::Instance(); return sc->psf.GetFlexibleMemorySize(); } bool SystemContentGetIconPath(std::filesystem::path* path) { if (path == nullptr) { return false; } auto* sc = Common::Singleton::Instance(); if (sc->icon_path.empty()) { return false; } *path = sc->icon_path; return true; } bool SystemContentGetChunksNum(uint32_t* num) { if (num == nullptr) { return false; } auto* sc = Common::Singleton::Instance(); return sc->playgo.GetChunksNum(num); } } // namespace Loader