diff --git a/include/stratosphere.hpp b/include/stratosphere.hpp
index 2e8ec8bf..0a136366 100644
--- a/include/stratosphere.hpp
+++ b/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/include/stratosphere/cfg/cfg_api.hpp b/include/stratosphere/cfg/cfg_api.hpp
index 89181b82..747f7e35 100644
--- a/include/stratosphere/cfg/cfg_api.hpp
+++ b/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/include/stratosphere/cfg/cfg_locale_types.hpp b/include/stratosphere/cfg/cfg_locale_types.hpp
new file mode 100644
index 00000000..d281c6cf
--- /dev/null
+++ b/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/include/stratosphere/cfg/cfg_types.hpp b/include/stratosphere/cfg/cfg_types.hpp
index 530e4160..71cf7e20 100644
--- a/include/stratosphere/cfg/cfg_types.hpp
+++ b/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/include/stratosphere/settings.hpp b/include/stratosphere/settings.hpp
new file mode 100644
index 00000000..2751a061
--- /dev/null
+++ b/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/include/stratosphere/settings/settings_types.hpp b/include/stratosphere/settings/settings_types.hpp
new file mode 100644
index 00000000..994a3a85
--- /dev/null
+++ b/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/source/cfg/cfg_override.cpp b/source/cfg/cfg_override.cpp
index 525c960d..7e784660 100644
--- a/source/cfg/cfg_override.cpp
+++ b/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);