diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index b0487302b3..565decb390 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -64,7 +64,8 @@ @string/language_spanish @string/language_taiwanese @string/language_traditional_chinese - @string/language_serbian + @string/language_polish + @string/language_thai @@ -87,6 +88,7 @@ 11 16 18 + 19 @@ -407,6 +409,7 @@ @string/app_language_persian @string/app_language_hebrew @string/app_language_serbian + @string/app_language_thai 0 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 8a1193303e..a34d75d7cb 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -1011,7 +1011,8 @@ 简体中文 正體中文 Português do Brasil - српски + Polska + แบบไทย B @@ -1216,6 +1217,7 @@ فارسی עברית Српски + แบบไทย Theme Color diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index de54696497..638be4127f 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -120,7 +120,7 @@ ENUM(AudioMode, Mono, Stereo, Surround); ENUM(Language, Japanese, EnglishAmerican, French, German, Italian, Spanish, Chinese, Korean, Dutch, Portuguese, Russian, Taiwanese, EnglishBritish, FrenchCanadian, SpanishLatin, - ChineseSimplified, ChineseTraditional, PortugueseBrazilian, Serbian, Polish, Thai); + ChineseSimplified, ChineseTraditional, PortugueseBrazilian, Polish, Thai); ENUM(Region, Japan, Usa, Europe, Australia, China, Korea, Taiwan); ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt, Gb, GbEire, Gmt, GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica, diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index 1b38bcec12..e9681ab80f 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -41,71 +41,37 @@ const std::array LANGUAGE_NAMES{{ "Thai", }}; -namespace { -constexpr std::size_t LEGACY_LANGUAGE_REGION_SIZE = sizeof(std::array); -constexpr std::size_t PACKED_LANGUAGE_REGION_MAX_SIZE = sizeof(LanguageEntry) * 32; +namespace +{ + constexpr std::size_t MAX_EXPANDED_LANG_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; - } + bool InflateRawDeflate(std::span compressed, std::vector& out) + { + if (compressed.empty()) 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); + 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; } - 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)); - } - } + out.resize(MAX_EXPANDED_LANG_SIZE); + stream.next_out = reinterpret_cast(out.data()); + stream.avail_out = static_cast(out.size()); - inflateEnd(&stream); - return ret == Z_STREAM_END; -} + int ret = inflate(&stream, Z_FINISH); + inflateEnd(&stream); -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 (ret != Z_STREAM_END && ret != Z_OK) { + return false; + } - if (decompressed.size() < LEGACY_LANGUAGE_REGION_SIZE || - decompressed.size() % sizeof(LanguageEntry) != 0) { - return; + // Shrink to actual decompressed size + out.resize(stream.total_out); + return true; } - - std::memcpy(raw.language_entries.data(), decompressed.data(), LEGACY_LANGUAGE_REGION_SIZE); -} } // namespace std::string LanguageEntry::GetApplicationName() const { @@ -141,55 +107,48 @@ constexpr std::array language_to_codes = {{ NACP::NACP() = default; -NACP::NACP(VirtualFile file) { +NACP::NACP(VirtualFile file) +{ file->ReadObject(&raw); - DecodePackedLanguageEntries(raw); + if (raw.titles_data_format == TitleDataFormat::Compressed) { + const u16 compressed_size = raw.language_entries.compressed_data.buffer_size; + std::span compressed_payload{raw.language_entries.compressed_data.buffer, + compressed_size}; + + std::vector decompressed; + if (InflateRawDeflate(compressed_payload, decompressed)) { + const size_t entry_count = decompressed.size() / sizeof(LanguageEntry); + language_entries.resize(entry_count); + std::memcpy(language_entries.data(), decompressed.data(), decompressed.size()); + } + } else { + language_entries.resize(16); + std::memcpy(language_entries.data(), raw.language_entries.language_entries.data(), + sizeof(raw.language_entries.language_entries)); + } } NACP::~NACP() = default; const LanguageEntry& NACP::GetLanguageEntry() const { - auto const language = []{ - switch (Settings::values.language_index.GetValue()) { - case Settings::Language::Chinese: return Language::SimplifiedChinese; - case Settings::Language::ChineseSimplified: return Language::SimplifiedChinese; - case Settings::Language::ChineseTraditional: return Language::TraditionalChinese; - case Settings::Language::Dutch: return Language::Dutch; - case Settings::Language::EnglishAmerican: return Language::AmericanEnglish; - case Settings::Language::EnglishBritish: return Language::BritishEnglish; - case Settings::Language::French: return Language::French; - case Settings::Language::FrenchCanadian: return Language::CanadianFrench; - case Settings::Language::German: return Language::German; - case Settings::Language::Italian: return Language::Italian; - case Settings::Language::Korean: return Language::Korean; - case Settings::Language::Japanese: return Language::Japanese; - case Settings::Language::Portuguese: return Language::Portuguese; - case Settings::Language::PortugueseBrazilian: return Language::BrazilianPortuguese; - case Settings::Language::Russian: return Language::Russian; - case Settings::Language::Serbian: return Language::Russian; - case Settings::Language::Spanish: return Language::Spanish; - case Settings::Language::SpanishLatin: return Language::LatinAmericanSpanish; - case Settings::Language::Taiwanese: return Language::SimplifiedChinese; - case Settings::Language::Thai: return Language::Thai; - case Settings::Language::Polish: return Language::Polish; - } - }(); - { - const auto& language_entry = raw.language_entries.at(static_cast(language)); - if (!language_entry.GetApplicationName().empty()) - return language_entry; + u32 index = static_cast(Settings::values.language_index.GetValue()); + + if (index < language_entries.size()) { + return language_entries[index]; } - for (const auto& language_entry : raw.language_entries) { - if (!language_entry.GetApplicationName().empty()) - return language_entry; + + for (const auto& entry : language_entries) { + return entry; } - return raw.language_entries.at(static_cast(Language::AmericanEnglish)); + + return language_entries.at(static_cast(Language::AmericanEnglish)); } -std::array NACP::GetApplicationNames() const { - std::array names{}; - for (size_t i = 0; i < raw.language_entries.size(); ++i) { - names[i] = raw.language_entries[i].GetApplicationName(); +std::vector NACP::GetApplicationNames() const { + std::vector names; + names.reserve(language_entries.size()); + for (const auto& entry : language_entries) { + names.push_back(entry.GetApplicationName()); } return names; } diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 478fd6e5e7..7b90ba235d 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -27,6 +27,24 @@ struct LanguageEntry { }; static_assert(sizeof(LanguageEntry) == 0x300); +struct LanguageEntryData { + union { + // TitleDataFormat::Uncompressed (16 entries) + std::array language_entries; + + // TitleDataFormat::Compressed (18+ entries) + struct { + u16 buffer_size; + u8 buffer[0x2FFE]; + } compressed_data; + }; +}; + +enum class TitleDataFormat : u8 { + Uncompressed = 0, + Compressed = 1, +}; + struct ApplicationNeighborDetectionGroupConfiguration { u64 group_id; ///< GroupId std::array key; @@ -98,6 +116,17 @@ struct AccessibleLaunchRequiredVersion { }; static_assert(sizeof(AccessibleLaunchRequiredVersion) == 0x40); +enum class CrashReport : u8 { + Deny = 0, + Allow = 1, +}; + +enum class PlayLogQueryCapability : u8 { + None = 0, + WhiteList = 1, + All = 2, +}; + struct ApplicationControlDataConditionData { u8 priority; INSERT_PADDING_BYTES(7); @@ -154,7 +183,7 @@ enum class SupportedLanguage : u32 { // The raw file format of a NACP file. struct RawNACP { - std::array language_entries; + LanguageEntryData language_entries; std::array isbn; u8 startup_user_account; u8 user_account_switch_lock; @@ -165,7 +194,7 @@ struct RawNACP { bool screenshot_enabled; u8 video_capture_mode; bool data_loss_confirmation; - INSERT_PADDING_BYTES(1); + u8 play_log_policy; u64_le presence_group_id; std::array rating_age; std::array version_string; @@ -181,10 +210,14 @@ struct RawNACP { u8 logo_type; u8 logo_handling; bool runtime_add_on_content_install; - INSERT_PADDING_BYTES(5); + u8 runtime_parameter_delivery; + u8 appropriate_age_for_china; + INSERT_PADDING_BYTES(1); + CrashReport crash_report; u64_le seed_for_pseudo_device_id; std::array bcat_passphrase; - INSERT_PADDING_BYTES(7); + u8 startup_user_account_option; + INSERT_PADDING_BYTES(6); // ReservedForUserAccountSaveDataOperation u64_le user_account_save_data_max_size; u64_le user_account_save_data_max_journal_size; u64_le device_save_data_max_size; @@ -194,9 +227,15 @@ struct RawNACP { u64_le cache_storage_journal_size; u64_le cache_storage_data_and_journal_max_size; u16_le cache_storage_max_index; - INSERT_PADDING_BYTES(0x8B); + u8 runtime_upgrade; + u32_le supporting_limited_application_licenses; + std::array play_log_queryable_application_id; + PlayLogQueryCapability play_log_query_capability; + u8 repair_flag; + u8 program_index; + u8 required_network_service_license_on_launch_flag; u8 app_error_code_prefix; - u8 title_compression; + TitleDataFormat titles_data_format; u8 acd_index; ApparentPlatform apparent_platform; // NeighborDetectionClientConfiguration neighbor_detection_client_configuration; @@ -219,7 +258,8 @@ struct RawNACP { u8 has_ingame_voice_chat; INSERT_PADDING_BYTES(3); u32_le supported_extra_addon_content_flag; - INSERT_PADDING_BYTES(0x698); + u8 has_karaoke_feature; + INSERT_PADDING_BYTES(0x697); std::array platform_specific_region; }; static_assert(sizeof(RawNACP) == 0x4000, "RawNACP has incorrect size."); @@ -243,7 +283,7 @@ public: u64 GetDefaultNormalSaveSize() const; u64 GetDefaultJournalSaveSize() const; u32 GetSupportedLanguages() const; - std::array GetApplicationNames() const; + std::vector GetApplicationNames() const; std::vector GetRawBytes() const; bool GetUserAccountSwitchLock() const; u64 GetDeviceSaveDataSize() const; @@ -252,6 +292,7 @@ public: private: RawNACP raw{}; + std::vector language_entries; }; } // namespace FileSys diff --git a/src/core/hle/service/ns/language.cpp b/src/core/hle/service/ns/language.cpp index 997e293a80..ff5ccdd6d3 100644 --- a/src/core/hle/service/ns/language.cpp +++ b/src/core/hle/service/ns/language.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -329,9 +332,6 @@ constexpr ApplicationLanguagePriorityList priority_list_brazilian_portuguese = { constexpr ApplicationLanguagePriorityList priority_list_thai = {{ ApplicationLanguage::Thai, - ApplicationLanguage::BrazilianPortuguese, - ApplicationLanguage::Portuguese, - ApplicationLanguage::LatinAmericanSpanish, ApplicationLanguage::AmericanEnglish, ApplicationLanguage::BritishEnglish, ApplicationLanguage::Japanese, @@ -350,10 +350,6 @@ constexpr ApplicationLanguagePriorityList priority_list_thai = {{ constexpr ApplicationLanguagePriorityList priority_list_polish = {{ ApplicationLanguage::Polish, - ApplicationLanguage::Thai, - ApplicationLanguage::BrazilianPortuguese, - ApplicationLanguage::Portuguese, - ApplicationLanguage::LatinAmericanSpanish, ApplicationLanguage::AmericanEnglish, ApplicationLanguage::BritishEnglish, ApplicationLanguage::Japanese, diff --git a/src/core/hle/service/set/settings_types.h b/src/core/hle/service/set/settings_types.h index 12f1c42185..469b8d745c 100644 --- a/src/core/hle/service/set/settings_types.h +++ b/src/core/hle/service/set/settings_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -193,8 +196,8 @@ enum class LanguageCode : u64 { ZH_HANS = 0x00736E61482D687A, ZH_HANT = 0x00746E61482D687A, PT_BR = 0x00000052422D7470, - TH = 0x0000000000006874, PL = 0x000000000000706C, + TH = 0x0000000000006874, }; /// This is nn::settings::system::NotificationVolume @@ -252,7 +255,7 @@ enum class PlatformRegion : s32 { Terra = 2, }; -constexpr std::array available_language_codes = {{ +constexpr std::array available_language_codes = {{ LanguageCode::JA, LanguageCode::EN_US, LanguageCode::FR, @@ -271,9 +274,11 @@ constexpr std::array available_language_codes = {{ LanguageCode::ZH_HANS, LanguageCode::ZH_HANT, LanguageCode::PT_BR, + LanguageCode::PL, + LanguageCode::TH }}; -static constexpr std::array, 18> language_to_layout{{ +static constexpr std::array, 20> language_to_layout{{ {LanguageCode::JA, KeyboardLayout::Japanese}, {LanguageCode::EN_US, KeyboardLayout::EnglishUs}, {LanguageCode::FR, KeyboardLayout::French}, @@ -292,6 +297,8 @@ static constexpr std::array, 18> languag {LanguageCode::ZH_HANS, KeyboardLayout::ChineseSimplified}, {LanguageCode::ZH_HANT, KeyboardLayout::ChineseTraditional}, {LanguageCode::PT_BR, KeyboardLayout::Portuguese}, + {LanguageCode::PL, KeyboardLayout::EnglishUsInternational}, + {LanguageCode::TH, KeyboardLayout::EnglishUsInternational} }}; /// This is nn::settings::system::AccountNotificationFlag diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index 78fbce2347..9666092802 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -651,7 +651,6 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) PAIR(Language, ChineseSimplified, tr("Simplified Chinese")), PAIR(Language, ChineseTraditional, tr("Traditional Chinese (正體中文)")), PAIR(Language, PortugueseBrazilian, tr("Brazilian Portuguese (português do Brasil)")), - PAIR(Language, Serbian, tr("Serbian (српски)")), PAIR(Language, Polish, tr("Polish (polska)")), PAIR(Language, Thai, tr("Thai (แบบไทย)")), }});