diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 8bcb66ac35..4ac08726f7 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1219,6 +1219,7 @@ target_link_libraries(core PRIVATE fmt::fmt nlohmann_json::nlohmann_json RenderDoc::API + ZLIB::ZLIB MbedTLS::mbedcrypto${MBEDTLS_LIB_SUFFIX} MbedTLS::mbedtls${MBEDTLS_LIB_SUFFIX}) diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index 6f150ff583..7ca7703afa 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -4,6 +4,13 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include + #include "common/settings.h" #include "common/string_util.h" #include "common/swap.h" @@ -31,6 +38,73 @@ const std::array LANGUAGE_NAMES{{ "BrazilianPortuguese", }}; +namespace { +constexpr std::size_t LEGACY_LANGUAGE_REGION_SIZE = sizeof(std::array); +constexpr std::size_t PACKED_LANGUAGE_REGION_MAX_SIZE = sizeof(LanguageEntry) * 32; + +bool InflateRawDeflate(std::span compressed, std::vector& out) { + if (compressed.empty() || compressed.size() > std::numeric_limits::max()) { + return false; + } + + z_stream stream{}; + stream.next_in = const_cast(reinterpret_cast(compressed.data())); + stream.avail_in = static_cast(compressed.size()); + if (inflateInit2(&stream, -MAX_WBITS) != Z_OK) { + return false; + } + + std::array chunk{}; + int ret = Z_OK; + while (ret == Z_OK) { + stream.next_out = reinterpret_cast(chunk.data()); + stream.avail_out = static_cast(chunk.size()); + ret = inflate(&stream, Z_NO_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END) { + inflateEnd(&stream); + return false; + } + + const auto produced = chunk.size() - static_cast(stream.avail_out); + if (produced != 0) { + if (out.size() + produced > PACKED_LANGUAGE_REGION_MAX_SIZE) { + inflateEnd(&stream); + return false; + } + out.insert(out.end(), chunk.begin(), + chunk.begin() + static_cast(produced)); + } + } + + inflateEnd(&stream); + return ret == Z_STREAM_END; +} + +void DecodePackedLanguageEntries(RawNACP& raw) { + auto* packed_region = reinterpret_cast(raw.language_entries.data()); + u16_le compressed_size_le{}; + std::memcpy(&compressed_size_le, packed_region, sizeof(compressed_size_le)); + const auto compressed_size = static_cast(compressed_size_le); + + if (compressed_size == 0 || compressed_size > LEGACY_LANGUAGE_REGION_SIZE - sizeof(u16_le)) { + return; + } + + std::vector decompressed; + if (!InflateRawDeflate( + std::span(packed_region + sizeof(u16_le), compressed_size), decompressed)) { + return; + } + + if (decompressed.size() < LEGACY_LANGUAGE_REGION_SIZE || + decompressed.size() % sizeof(LanguageEntry) != 0) { + return; + } + + std::memcpy(raw.language_entries.data(), decompressed.data(), LEGACY_LANGUAGE_REGION_SIZE); +} +} // namespace + std::string LanguageEntry::GetApplicationName() const { return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(), application_name.size()); @@ -66,6 +140,7 @@ NACP::NACP() = default; NACP::NACP(VirtualFile file) { file->ReadObject(&raw); + DecodePackedLanguageEntries(raw); } NACP::~NACP() = default;