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 b553402628..6fbeebdb6c 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -1009,7 +1009,8 @@ 简体中文 正體中文 Português do Brasil - српски + Polski + ไทย B @@ -1214,6 +1215,7 @@ فارسی עברית Српски + ไทย Theme Color diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index d4935d9b6d..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); + 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 7ca7703afa..7f7ccc073f 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -15,11 +15,13 @@ #include "common/string_util.h" #include "common/swap.h" #include "core/file_sys/control_metadata.h" + +#include "common/logging/log.h" #include "core/file_sys/vfs/vfs.h" namespace FileSys { -const std::array LANGUAGE_NAMES{{ +const std::array LANGUAGE_NAMES{{ "AmericanEnglish", "BritishEnglish", "Japanese", @@ -36,16 +38,16 @@ const std::array LANGUAGE_NAMES{{ "TraditionalChinese", "SimplifiedChinese", "BrazilianPortuguese", + "Polish", + "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; +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; - } +bool InflateRawDeflate(std::span compressed, std::vector& out) +{ + if (compressed.empty()) return false; z_stream stream{}; stream.next_in = const_cast(reinterpret_cast(compressed.data())); @@ -54,54 +56,20 @@ bool InflateRawDeflate(std::span compressed, std::vector& out) { 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)); - } - } + out.resize(MAX_EXPANDED_LANG_SIZE); + stream.next_out = reinterpret_cast(out.data()); + stream.avail_out = static_cast(out.size()); + int ret = inflate(&stream, Z_FINISH); 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; + if (ret != Z_STREAM_END && ret != Z_OK) { + return false; } - std::memcpy(raw.language_entries.data(), decompressed.data(), LEGACY_LANGUAGE_REGION_SIZE); + // Shrink to actual decompressed size + out.resize(stream.total_out); + return true; } } // namespace @@ -115,7 +83,7 @@ std::string LanguageEntry::GetDeveloperName() const { developer_name.size()); } -constexpr std::array language_to_codes = {{ +constexpr std::array language_to_codes = {{ Language::Japanese, Language::AmericanEnglish, Language::French, @@ -134,39 +102,54 @@ constexpr std::array language_to_codes = {{ Language::SimplifiedChinese, Language::TraditionalChinese, Language::BrazilianPortuguese, + Language::Polish, + Language::Thai }}; 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 { - Language language = - language_to_codes[static_cast(Settings::values.language_index.GetValue())]; + u32 index = static_cast(Settings::values.language_index.GetValue()); - { - const auto& language_entry = raw.language_entries.at(static_cast(language)); - if (!language_entry.GetApplicationName().empty()) - return language_entry; + 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 bd109f783f..e0e0bb90a5 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -27,9 +27,29 @@ struct LanguageEntry { }; static_assert(sizeof(LanguageEntry) == 0x300, "LanguageEntry has incorrect size."); +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 TitleDataFormat : u8 { + Uncompressed = 0, + Compressed = 1, +}; + // 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; @@ -40,7 +60,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; @@ -55,11 +75,15 @@ struct RawNACP { std::array local_communication; u8 logo_type; u8 logo_handling; - bool runtime_add_on_content_install; - INSERT_PADDING_BYTES(5); + u8 runtime_add_on_content_install; + u8 runtime_parameter_delivery; + u8 appropriate_age_for_china; + INSERT_PADDING_BYTES(1); + u8 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; @@ -69,9 +93,16 @@ 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 app_error_code_prefix; INSERT_PADDING_BYTES(1); + u8 runtime_upgrade; + u32_le supporting_limited_application_licenses; + std::array play_log_queryable_application_id; + u8 play_log_query_capability; + u8 repair_flag; + u8 program_index; + u8 required_network_service_license_on_launch_flag; + u8 app_error_code_prefix; + TitleDataFormat titles_data_format; u8 acd_index; u8 apparent_platform; INSERT_PADDING_BYTES(0x22F); @@ -85,7 +116,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."); @@ -108,11 +140,13 @@ enum class Language : u8 { TraditionalChinese = 13, SimplifiedChinese = 14, BrazilianPortuguese = 15, + Polish = 16, + Thai = 17, Default = 255, }; -extern const std::array LANGUAGE_NAMES; +extern const std::array LANGUAGE_NAMES; // A class representing the format used by NX metadata files, typically named Control.nacp. // These store application name, dev name, title id, and other miscellaneous data. @@ -131,7 +165,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; @@ -140,6 +174,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 d187be935a..b4e82d18c9 100644 --- a/src/core/hle/service/ns/language.cpp +++ b/src/core/hle/service/ns/language.cpp @@ -295,6 +295,44 @@ constexpr ApplicationLanguagePriorityList priority_list_brazilian_portuguese = { ApplicationLanguage::TraditionalChinese, }}; +constexpr ApplicationLanguagePriorityList priority_list_polish = {{ + ApplicationLanguage::Polish, + ApplicationLanguage::AmericanEnglish, + ApplicationLanguage::BritishEnglish, + ApplicationLanguage::LatinAmericanSpanish, + ApplicationLanguage::CanadianFrench, + ApplicationLanguage::French, + ApplicationLanguage::German, + ApplicationLanguage::Spanish, + ApplicationLanguage::Italian, + ApplicationLanguage::Dutch, + ApplicationLanguage::Portuguese, + ApplicationLanguage::Russian, + ApplicationLanguage::Japanese, + ApplicationLanguage::SimplifiedChinese, + ApplicationLanguage::TraditionalChinese, + ApplicationLanguage::Korean, + }}; + +constexpr ApplicationLanguagePriorityList priority_list_thai = {{ + ApplicationLanguage::Thai, + ApplicationLanguage::AmericanEnglish, + ApplicationLanguage::BritishEnglish, + ApplicationLanguage::LatinAmericanSpanish, + ApplicationLanguage::CanadianFrench, + ApplicationLanguage::French, + ApplicationLanguage::German, + ApplicationLanguage::Spanish, + ApplicationLanguage::Italian, + ApplicationLanguage::Dutch, + ApplicationLanguage::Portuguese, + ApplicationLanguage::Russian, + ApplicationLanguage::Japanese, + ApplicationLanguage::SimplifiedChinese, + ApplicationLanguage::TraditionalChinese, + ApplicationLanguage::Korean, + }}; + const ApplicationLanguagePriorityList* GetApplicationLanguagePriorityList( const ApplicationLanguage lang) { switch (lang) { @@ -330,6 +368,10 @@ const ApplicationLanguagePriorityList* GetApplicationLanguagePriorityList( return &priority_list_simplified_chinese; case ApplicationLanguage::BrazilianPortuguese: return &priority_list_brazilian_portuguese; + case ApplicationLanguage::Polish: + return &priority_list_polish; + case ApplicationLanguage::Thai: + return &priority_list_thai; default: return nullptr; } @@ -372,6 +414,10 @@ std::optional ConvertToApplicationLanguage( return ApplicationLanguage::SimplifiedChinese; case Set::LanguageCode::PT_BR: return ApplicationLanguage::BrazilianPortuguese; + case Set::LanguageCode::PL: + return ApplicationLanguage::Polish; + case Set::LanguageCode::TH: + return ApplicationLanguage::Thai; default: return std::nullopt; } @@ -411,6 +457,10 @@ std::optional ConvertToLanguageCode(const ApplicationLanguage return Set::LanguageCode::ZH_HANS; case ApplicationLanguage::BrazilianPortuguese: return Set::LanguageCode::PT_BR; + case ApplicationLanguage::Polish: + return Set::LanguageCode::PL; + case ApplicationLanguage::Thai: + return Set::LanguageCode::TH; default: return std::nullopt; } diff --git a/src/core/hle/service/ns/language.h b/src/core/hle/service/ns/language.h index dad9934f2e..3502c5c093 100644 --- a/src/core/hle/service/ns/language.h +++ b/src/core/hle/service/ns/language.h @@ -26,6 +26,8 @@ enum class ApplicationLanguage : u8 { TraditionalChinese, SimplifiedChinese, BrazilianPortuguese, + Polish, + Thai, Count }; using ApplicationLanguagePriorityList = diff --git a/src/core/hle/service/set/settings_types.h b/src/core/hle/service/set/settings_types.h index 7f152cb523..831fb26519 100644 --- a/src/core/hle/service/set/settings_types.h +++ b/src/core/hle/service/set/settings_types.h @@ -169,6 +169,8 @@ enum class Language : u32 { SimplifiedCHhinese, TraditionalChinese, BrazilianPortuguese, + Polish, + Thai }; /// This is "nn::settings::LanguageCode", which is a NUL-terminated string stored in a u64. @@ -191,6 +193,8 @@ enum class LanguageCode : u64 { ZH_HANS = 0x00736E61482D687A, ZH_HANT = 0x00746E61482D687A, PT_BR = 0x00000052422D7470, + PL = 0x0000000000006C70, + TH = 0x0000000000006874, }; /// This is nn::settings::system::NotificationVolume @@ -248,7 +252,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, @@ -267,9 +271,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}, @@ -288,6 +294,9 @@ 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 f4355197b0..e1168d0e9d 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -651,7 +651,8 @@ 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 (Polski)")), + PAIR(Language, Thai, tr("Thai (ไทย)")), }}); translations->insert({Settings::EnumMetadata::Index(), {