diff --git a/stratosphere/ams_mitm/source/amsmitm_module_management.cpp b/stratosphere/ams_mitm/source/amsmitm_module_management.cpp index 03eb32aa6..99c9bad6b 100644 --- a/stratosphere/ams_mitm/source/amsmitm_module_management.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_module_management.cpp @@ -60,7 +60,7 @@ namespace ams::mitm { constexpr ModuleDefinition g_module_definitions[ModuleId_Count] = { GetModuleDefinition(), - GetModuleDefinition(), + GetModuleDefinition(), GetModuleDefinition(), GetModuleDefinition(), GetModuleDefinition(), diff --git a/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.cpp b/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.cpp new file mode 100644 index 000000000..e50223880 --- /dev/null +++ b/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "set_mitm_service.hpp" + +namespace ams::mitm::settings { + + using namespace ams::settings; + + Result SetMitmService::EnsureLocale() { + std::scoped_lock lk(this->lock); + + const bool is_ns = this->client_info.program_id == ncm::ProgramId::Ns; + + if (!this->got_locale) { + std::memset(&this->locale, 0xCC, sizeof(this->locale)); + ncm::ProgramId program_id = this->client_info.program_id; + if (program_id == ncm::ProgramId::Ns) { + /* When NS asks for a locale, refresh to get the current application locale. */ + os::ProcessId application_process_id; + R_TRY(pm::dmnt::GetApplicationProcessId(&application_process_id)); + R_TRY(pm::info::GetProgramId(&program_id, application_process_id)); + } + this->locale = cfg::GetOverrideLocale(program_id); + this->got_locale = !is_ns; + } + + return ResultSuccess(); + } + + Result SetMitmService::GetLanguageCode(sf::Out out) { + this->EnsureLocale(); + + /* If there's no override locale, just use the actual one. */ + R_UNLESS(settings::IsValidLanguageCode(this->locale.language_code), sm::mitm::ResultShouldForwardToSession()); + + out.SetValue(this->locale.language_code); + return ResultSuccess(); + } + + Result SetMitmService::GetRegionCode(sf::Out out) { + this->EnsureLocale(); + + /* If there's no override locale, just use the actual one. */ + R_UNLESS(settings::IsValidRegionCode(this->locale.region_code), sm::mitm::ResultShouldForwardToSession()); + + out.SetValue(this->locale.region_code); + return ResultSuccess(); + } + +} diff --git a/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.hpp b/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.hpp index 600e32957..66610b8e4 100644 --- a/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.hpp +++ b/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.hpp @@ -16,25 +16,39 @@ #pragma once #include -namespace ams::mitm::set { +namespace ams::mitm::settings { class SetMitmService : public sf::IMitmServiceObject { private: enum class CommandId { - /* TODO */ + GetLanguageCode = 0, + GetRegionCode = 4, }; + private: + os::Mutex lock; + cfg::OverrideLocale locale; + bool got_locale; public: static bool ShouldMitm(const sm::MitmProcessInfo &client_info) { - /* TODO */ - return false; + /* We will mitm: + * - ns and games, to allow for overriding game locales. + */ + const bool is_game = (ncm::IsApplicationProgramId(client_info.program_id) && !client_info.override_status.IsHbl()); + return client_info.program_id == ncm::ProgramId::Ns || is_game; } public: - SF_MITM_SERVICE_OBJECT_CTOR(SetMitmService) { /* ... */ } + SF_MITM_SERVICE_OBJECT_CTOR(SetMitmService) { + this->got_locale = false; + } + private: + Result EnsureLocale(); protected: - /* TODO */ + Result GetLanguageCode(sf::Out out); + Result GetRegionCode(sf::Out out); public: DEFINE_SERVICE_DISPATCH_TABLE { - /* TODO */ + MAKE_SERVICE_COMMAND_META(GetLanguageCode), + MAKE_SERVICE_COMMAND_META(GetRegionCode), }; }; diff --git a/stratosphere/ams_mitm/source/set_mitm/setmitm_module.cpp b/stratosphere/ams_mitm/source/set_mitm/setmitm_module.cpp index 8e23ff93d..01b429b8d 100644 --- a/stratosphere/ams_mitm/source/set_mitm/setmitm_module.cpp +++ b/stratosphere/ams_mitm/source/set_mitm/setmitm_module.cpp @@ -17,7 +17,7 @@ #include "set_mitm_service.hpp" #include "setsys_mitm_service.hpp" -namespace ams::mitm::set { +namespace ams::mitm::settings { namespace { diff --git a/stratosphere/ams_mitm/source/set_mitm/setmitm_module.hpp b/stratosphere/ams_mitm/source/set_mitm/setmitm_module.hpp index cea2a1d44..a0a578ede 100644 --- a/stratosphere/ams_mitm/source/set_mitm/setmitm_module.hpp +++ b/stratosphere/ams_mitm/source/set_mitm/setmitm_module.hpp @@ -17,7 +17,7 @@ #include #include "../amsmitm_module.hpp" -namespace ams::mitm::set { +namespace ams::mitm::settings { DEFINE_MITM_MODULE_CLASS(0x8000, 43); diff --git a/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp b/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp index af76dc8ab..33189ec62 100644 --- a/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp +++ b/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp @@ -16,7 +16,7 @@ #pragma once #include -namespace ams::mitm::set { +namespace ams::mitm::settings { class SetSysMitmService : public sf::IMitmServiceObject { private: diff --git a/stratosphere/libstratosphere/include/stratosphere.hpp b/stratosphere/libstratosphere/include/stratosphere.hpp index 2e8ec8bfc..0a1363660 100644 --- a/stratosphere/libstratosphere/include/stratosphere.hpp +++ b/stratosphere/libstratosphere/include/stratosphere.hpp @@ -43,6 +43,7 @@ #include "stratosphere/reg.hpp" #include "stratosphere/rnd.hpp" #include "stratosphere/ro.hpp" +#include "stratosphere/settings.hpp" #include "stratosphere/sf.hpp" #include "stratosphere/sm.hpp" #include "stratosphere/spl.hpp" diff --git a/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_api.hpp b/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_api.hpp index 89181b825..747f7e355 100644 --- a/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_api.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_api.hpp @@ -15,6 +15,7 @@ */ #pragma once #include "cfg_types.hpp" +#include "cfg_locale_types.hpp" namespace ams::cfg { @@ -31,6 +32,9 @@ namespace ams::cfg { /* Override key utilities. */ OverrideStatus CaptureOverrideStatus(ncm::ProgramId program_id); + /* Locale utilities. */ + OverrideLocale GetOverrideLocale(ncm::ProgramId program_id); + /* Flag utilities. */ bool HasFlag(ncm::ProgramId program_id, const char *flag); bool HasContentSpecificFlag(ncm::ProgramId program_id, const char *flag); diff --git a/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_locale_types.hpp b/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_locale_types.hpp new file mode 100644 index 000000000..d281c6cfa --- /dev/null +++ b/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_locale_types.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include "cfg_types.hpp" +#include "../settings/settings_types.hpp" + +namespace ams::cfg { + + struct OverrideLocale { + settings::LanguageCode language_code; + settings::RegionCode region_code; + }; + +} diff --git a/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_types.hpp b/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_types.hpp index 530e41609..71cf7e20a 100644 --- a/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_types.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_types.hpp @@ -52,7 +52,7 @@ namespace ams::cfg { static_assert(std::is_pod::value, "std::is_pod::value"); constexpr inline bool operator==(const OverrideStatus &lhs, const OverrideStatus &rhs) { - return lhs.keys_held == rhs.keys_held && lhs.flags == rhs.flags; + return std::memcmp(&lhs, &rhs, sizeof(lhs)) == 0; } constexpr inline bool operator!=(const OverrideStatus &lhs, const OverrideStatus &rhs) { diff --git a/stratosphere/libstratosphere/include/stratosphere/settings.hpp b/stratosphere/libstratosphere/include/stratosphere/settings.hpp new file mode 100644 index 000000000..2751a061b --- /dev/null +++ b/stratosphere/libstratosphere/include/stratosphere/settings.hpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "settings/settings_types.hpp" diff --git a/stratosphere/libstratosphere/include/stratosphere/settings/settings_types.hpp b/stratosphere/libstratosphere/include/stratosphere/settings/settings_types.hpp new file mode 100644 index 000000000..994a3a85a --- /dev/null +++ b/stratosphere/libstratosphere/include/stratosphere/settings/settings_types.hpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +namespace ams::settings { + + enum Language { + Language_Japanese, + Language_AmericanEnglish, + Language_French, + Language_German, + Language_Italian, + Language_Spanish, + Language_Chinese, + Language_Korean, + Language_Dutch, + Language_Portuguese, + Language_Russian, + Language_Taiwanese, + Language_BritishEnglish, + Language_CanadianFrench, + Language_LatinAmericanSpanish, + /* 4.0.0+ */ + Language_SimplifiedChinese, + Language_TraditionalChinese, + + Language_Count, + }; + + struct LanguageCode { + static constexpr size_t MaxLength = 8; + + char name[MaxLength]; + + static constexpr LanguageCode Encode(const char *name, size_t name_size) { + LanguageCode out{}; + for (size_t i = 0; i < MaxLength && i < name_size; i++) { + out.name[i] = name[i]; + } + return out; + } + + static constexpr LanguageCode Encode(const char *name) { + return Encode(name, std::strlen(name)); + } + + template + static constexpr inline LanguageCode EncodeLanguage = [] { + if constexpr (false) { /* ... */ } + #define AMS_MATCH_LANGUAGE(lang, enc) else if constexpr (Lang == Language_##lang) { return LanguageCode::Encode(enc); } + AMS_MATCH_LANGUAGE(Japanese, "ja") + AMS_MATCH_LANGUAGE(AmericanEnglish, "en-US") + AMS_MATCH_LANGUAGE(French, "fr") + AMS_MATCH_LANGUAGE(German, "de") + AMS_MATCH_LANGUAGE(Italian, "it") + AMS_MATCH_LANGUAGE(Spanish, "es") + AMS_MATCH_LANGUAGE(Chinese, "zh-CN") + AMS_MATCH_LANGUAGE(Korean, "ko") + AMS_MATCH_LANGUAGE(Dutch, "nl") + AMS_MATCH_LANGUAGE(Portuguese, "pt") + AMS_MATCH_LANGUAGE(Russian, "ru") + AMS_MATCH_LANGUAGE(Taiwanese, "zh-TW") + AMS_MATCH_LANGUAGE(BritishEnglish, "en-GB") + AMS_MATCH_LANGUAGE(CanadianFrench, "fr-CA") + AMS_MATCH_LANGUAGE(LatinAmericanSpanish, "es-419") + /* 4.0.0+ */ + AMS_MATCH_LANGUAGE(SimplifiedChinese, "zh-Hans") + AMS_MATCH_LANGUAGE(TraditionalChinese, "zh-Hant") + #undef AMS_MATCH_LANGUAGE + else { static_assert(Lang != Language_Japanese); } + }(); + + static constexpr inline LanguageCode Encode(const Language language) { + constexpr LanguageCode EncodedLanguages[Language_Count] = { + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + EncodeLanguage, + /* 4.0.0+ */ + EncodeLanguage, + EncodeLanguage, + }; + return EncodedLanguages[language]; + } + + }; + + constexpr inline bool operator==(const LanguageCode &lhs, const LanguageCode &rhs) { + return std::strncmp(lhs.name, rhs.name, sizeof(lhs)) == 0; + } + + constexpr inline bool operator!=(const LanguageCode &lhs, const LanguageCode &rhs) { + return !(lhs == rhs); + } + + constexpr inline bool operator==(const LanguageCode &lhs, const Language &rhs) { + return lhs == LanguageCode::Encode(rhs); + } + + constexpr inline bool operator!=(const LanguageCode &lhs, const Language &rhs) { + return !(lhs == rhs); + } + + constexpr inline bool operator==(const Language &lhs, const LanguageCode &rhs) { + return rhs == lhs; + } + + constexpr inline bool operator!=(const Language &lhs, const LanguageCode &rhs) { + return !(lhs == rhs); + } + + namespace impl { + + template + constexpr inline bool IsValidLanguageCode(const LanguageCode &lc, std::index_sequence) { + return ((lc == LanguageCode::Encode(static_cast(Is))) || ...); + } + + } + + constexpr inline bool IsValidLanguageCodeDeprecated(const LanguageCode &lc) { + return impl::IsValidLanguageCode(lc, std::make_index_sequence{}); + } + + constexpr inline bool IsValidLanguageCode(const LanguageCode &lc) { + return impl::IsValidLanguageCode(lc, std::make_index_sequence{}); + } + + static_assert(std::is_pod::value); + static_assert(sizeof(LanguageCode) == sizeof(u64)); + + /* Not an official type, but convenient. */ + enum RegionCode : s32 { + RegionCode_Japan, + RegionCode_America, + RegionCode_Europe, + RegionCode_Australia, + RegionCode_China, + RegionCode_Korea, + RegionCode_Taiwan, + + RegionCode_Count, + }; + + constexpr inline bool IsValidRegionCode(const RegionCode rc) { + return 0 <= rc && rc < RegionCode_Count; + } + +} diff --git a/stratosphere/libstratosphere/source/cfg/cfg_override.cpp b/stratosphere/libstratosphere/source/cfg/cfg_override.cpp index 525c960d1..7e784660e 100644 --- a/stratosphere/libstratosphere/source/cfg/cfg_override.cpp +++ b/stratosphere/libstratosphere/source/cfg/cfg_override.cpp @@ -36,6 +36,7 @@ namespace ams::cfg { struct ContentSpecificOverrideConfig { OverrideKey override_key; OverrideKey cheat_enable_key; + OverrideLocale locale; }; /* Override globals. */ @@ -164,10 +165,29 @@ namespace ams::cfg { config->override_key = ParseOverrideKey(value); } else if (strcasecmp(name, "cheat_enable_key") == 0) { config->cheat_enable_key = ParseOverrideKey(value); + } else if (strcasecmp(name, "override_language") == 0) { + config->locale.language_code = settings::LanguageCode::Encode(value); + } else if (strcasecmp(name, "override_region") == 0) { + if (strcasecmp(value, "jpn") == 0) { + config->locale.region_code = settings::RegionCode_Japan; + } else if (strcasecmp(value, "usa") == 0) { + config->locale.region_code = settings::RegionCode_America; + } else if (strcasecmp(value, "eur") == 0) { + config->locale.region_code = settings::RegionCode_Europe; + } else if (strcasecmp(value, "aus") == 0) { + config->locale.region_code = settings::RegionCode_Australia; + } else if (strcasecmp(value, "chn") == 0) { + config->locale.region_code = settings::RegionCode_China; + } else if (strcasecmp(value, "kor") == 0) { + config->locale.region_code = settings::RegionCode_Korea; + } else if (strcasecmp(value, "twn") == 0) { + config->locale.region_code = settings::RegionCode_Taiwan; + } } } else { return 0; } + return 1; } @@ -215,6 +235,8 @@ namespace ams::cfg { .override_key = g_default_override_key, .cheat_enable_key = g_default_cheat_enable_key, }; + std::memset(&config.locale, 0xCC, sizeof(config.locale)); + ParseIniFile(ContentSpecificIniHandler, path, &config); return config; } @@ -264,6 +286,10 @@ namespace ams::cfg { return status; } + OverrideLocale GetOverrideLocale(ncm::ProgramId program_id) { + return GetContentOverrideConfig(program_id).locale; + } + /* HBL Configuration utilities. */ bool IsHblProgramId(ncm::ProgramId program_id) { return IsApplicationHblProgramId(program_id) || IsSpecificHblProgramId(program_id);