[file_sys] added packed language entries support (#3585)

Normally,  language_entries contained plain UTF-8 names.
In BotW v1.9.0, that title block is no longer directly readable as old UTF-8, so we were parsing binary data as text and going nuts.
...
In patch_manager.cpp (ParseControlNCA), I added a validity check: If update title text is unreadable, we keep update metadata but copy only the base language_entries block (0x0000..0x2FFF) so the game name is valid again.

UPDATE:
managed to decode the new language entries is a raw headerless zlib deflate.
added support for proper detection and inflation.

Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3585
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
This commit is contained in:
xbzk 2026-02-20 23:28:24 +01:00 committed by crueter
parent 93eecca894
commit 732b7eb560
No known key found for this signature in database
GPG Key ID: 425ACD2D4830EBC6
2 changed files with 76 additions and 0 deletions

View File

@ -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})

View File

@ -4,6 +4,13 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <cstddef>
#include <cstring>
#include <limits>
#include <span>
#include <zlib.h>
#include "common/settings.h"
#include "common/string_util.h"
#include "common/swap.h"
@ -31,6 +38,73 @@ const std::array<const char*, 16> LANGUAGE_NAMES{{
"BrazilianPortuguese",
}};
namespace {
constexpr std::size_t LEGACY_LANGUAGE_REGION_SIZE = sizeof(std::array<LanguageEntry, 16>);
constexpr std::size_t PACKED_LANGUAGE_REGION_MAX_SIZE = sizeof(LanguageEntry) * 32;
bool InflateRawDeflate(std::span<const u8> compressed, std::vector<u8>& out) {
if (compressed.empty() || compressed.size() > std::numeric_limits<uInt>::max()) {
return false;
}
z_stream stream{};
stream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(compressed.data()));
stream.avail_in = static_cast<uInt>(compressed.size());
if (inflateInit2(&stream, -MAX_WBITS) != Z_OK) {
return false;
}
std::array<u8, 0x1000> chunk{};
int ret = Z_OK;
while (ret == Z_OK) {
stream.next_out = reinterpret_cast<Bytef*>(chunk.data());
stream.avail_out = static_cast<uInt>(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<std::size_t>(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<std::ptrdiff_t>(produced));
}
}
inflateEnd(&stream);
return ret == Z_STREAM_END;
}
void DecodePackedLanguageEntries(RawNACP& raw) {
auto* packed_region = reinterpret_cast<u8*>(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<std::size_t>(compressed_size_le);
if (compressed_size == 0 || compressed_size > LEGACY_LANGUAGE_REGION_SIZE - sizeof(u16_le)) {
return;
}
std::vector<u8> decompressed;
if (!InflateRawDeflate(
std::span<const u8>(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;