diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 6542109949..1b698d2a17 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -93,12 +93,21 @@ void UpdatePointer() env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetUpdateTouchPointer()); } +std::vector Host_GetPreferredLocales() +{ + // We would like to call ConfigurationCompat.getLocales here, but this function gets called + // during dynamic initialization, and it seems like that makes us unable to obtain a JNIEnv. + return {}; +} + void Host_NotifyMapLoaded() { } + void Host_RefreshDSPDebuggerWindow() { } + bool Host_UIBlocksControllerState() { return false; diff --git a/Source/Core/Core/BootManager.cpp b/Source/Core/Core/BootManager.cpp index 78b191e1e7..f622c680fa 100644 --- a/Source/Core/Core/BootManager.cpp +++ b/Source/Core/Core/BootManager.cpp @@ -393,9 +393,8 @@ bool BootCore(std::unique_ptr boot, const WindowSystemInfo& wsi) // Override out-of-region languages/countries to prevent games from crashing or behaving oddly if (!StartUp.bOverrideRegionSettings) { - const int gc_language = - static_cast(StartUp.GetLanguageAdjustedForRegion(false, StartUp.m_region)); - StartUp.SelectedLanguage = gc_language - (gc_language > 0); + StartUp.SelectedLanguage = + DiscIO::ToGameCubeLanguage(StartUp.GetLanguageAdjustedForRegion(false, StartUp.m_region)); if (StartUp.bWii) { diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index d14d998462..5cf611024f 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -16,6 +16,8 @@ add_library(core BootManager.h CheatCodes.h CommonTitles.h + Config/DefaultLocale.cpp + Config/DefaultLocale.h Config/FreeLookSettings.cpp Config/FreeLookSettings.h Config/GraphicsSettings.cpp diff --git a/Source/Core/Core/Config/DefaultLocale.cpp b/Source/Core/Core/Config/DefaultLocale.cpp new file mode 100644 index 0000000000..af67ea63b7 --- /dev/null +++ b/Source/Core/Core/Config/DefaultLocale.cpp @@ -0,0 +1,199 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/Config/DefaultLocale.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#include "Common/Assert.h" +#include "Common/CommonTypes.h" +#include "Common/StringUtil.h" +#include "Core/Host.h" +#include "DiscIO/Enums.h" + +namespace Config +{ +static std::vector GetPreferredLocales() +{ + static const std::vector locales = Host_GetPreferredLocales(); + return locales; +} + +static std::optional TryParseLanguage(const std::string& locale) +{ + const std::vector split_locale = SplitString(locale, '-'); + if (split_locale.empty()) + return std::nullopt; + + // Special handling of Chinese due to its two writing systems + if (split_locale[0] == "zh") + { + const auto locale_contains = [&split_locale](std::string_view str) { + return std::find(split_locale.cbegin(), split_locale.cend(), str) != split_locale.cend(); + }; + + if (locale_contains("Hans")) + return DiscIO::Language::SimplifiedChinese; + if (locale_contains("Hant")) + return DiscIO::Language::TraditionalChinese; + + // Mainland China and Singapore use simplified characters + if (locale_contains("CN") || locale_contains("SG")) + return DiscIO::Language::SimplifiedChinese; + else + return DiscIO::Language::TraditionalChinese; + } + + // Same order as in Wii SYSCONF + constexpr std::array LANGUAGES = { + "ja", "en", "de", "fr", "es", "it", "nl", "zh", "zh", "ko", + }; + + const auto it = std::find(LANGUAGES.cbegin(), LANGUAGES.cend(), split_locale[0]); + if (it == LANGUAGES.cend()) + return std::nullopt; + + return static_cast(it - LANGUAGES.cbegin()); +} + +static DiscIO::Language ComputeDefaultLanguage() +{ + for (const std::string& locale : GetPreferredLocales()) + { + if (const std::optional language = TryParseLanguage(locale)) + return *language; + } + + return DiscIO::Language::English; +} + +static std::optional TryParseCountryCode(const std::string& locale) +{ + const auto is_upper = [](char c) { return std::isupper(c, std::locale::classic()); }; + + for (const std::string& part : SplitString(locale, '-')) + { + if (part.size() == 2 && is_upper(part[0]) && is_upper(part[1])) + return part; + } + + return std::nullopt; +} + +static std::string ComputeDefaultCountryCode() +{ +#ifdef _WIN32 + // Windows codepath: Check the regional information. + // More likely to match the user's physical location than locales are. + // TODO: Can we use GetUserDefaultGeoName? (It was added in a Windows 10 update) + GEOID geo = GetUserGeoID(GEOCLASS_NATION); + const int buffer_size = GetGeoInfoW(geo, GEO_ISO2, nullptr, 0, 0); + std::vector buffer(buffer_size); + const int result = GetGeoInfoW(geo, GEO_ISO2, buffer.data(), buffer_size, 0); + if (result != 0) + return TStrToUTF8(buffer.data()); +#endif + + // Generic codepath: Check the locales. + // Might be entirely unrelated to someone's actual country (for instance someone in a + // non-English-speaking country using en-US), but could also be a perfect match. + for (const std::string& locale : GetPreferredLocales()) + { + if (const std::optional country_code = TryParseCountryCode(locale)) + return *country_code; + } + + return ""; +} + +static std::optional ComputeDefaultCountry() +{ + // clang-format off + // Same order as in Wii SYSCONF + static constexpr std::array COUNTRIES = { + "--", "JP", "--", "--", "--", "--", "--", "--", "AI", "AG", "AR", "AW", "BS", "BB", "BZ", "BO", + "BR", "VG", "CA", "KY", "CL", "CO", "CR", "DM", "DO", "EC", "SV", "GF", "GD", "GP", "GT", "GY", + "HT", "HN", "JM", "MQ", "MX", "MS", "AN", "NI", "PA", "PY", "PE", "KN", "LC", "VC", "SR", "TT", + "TC", "US", "UY", "VI", "VE", "--", "--", "--", "--", "--", "--", "--", "--", "--", "--", "--", + "AL", "AU", "AT", "BE", "BA", "BW", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", + "HU", "IS", "IE", "IT", "LV", "LS", "LI", "LT", "LU", "MK", "MT", "ME", "MZ", "NA", "NL", "NZ", + "NO", "PL", "PT", "RO", "RU", "RS", "SK", "SI", "ZA", "ES", "SZ", "SE", "CH", "TR", "GB", "ZM", + "ZW", "AZ", "MR", "ML", "NE", "TD", "SD", "ER", "DJ", "SO", "--", "--", "--", "--", "--", "--", + "TW", "--", "--", "--", "--", "--", "--", "--", "KR", "--", "--", "--", "--", "--", "--", "--", + "HK", "MO", "--", "--", "--", "--", "--", "--", "ID", "SG", "TH", "PH", "MY", "--", "--", "--", + "CN", "--", "--", "--", "--", "--", "--", "--", "AE", "IN", "EG", "OM", "QA", "KW", "SA", "SY", + "BH", "JO", + }; + // clang-format on + + std::string country = ComputeDefaultCountryCode(); + + // Netherlands Antilles was split into three new codes after the release of the Wii + if (country == "BQ" || country == "CW" || country == "SX") + country = "AN"; + + const auto it = std::find(COUNTRIES.cbegin(), COUNTRIES.cend(), country); + if (it == COUNTRIES.cend()) + return std::nullopt; + + return static_cast(it - COUNTRIES.cbegin()); +} + +static DiscIO::Region ComputeDefaultRegion() +{ + const std::optional country = GetDefaultCountry(); + + if (country) + { + const DiscIO::Region region = DiscIO::SysConfCountryToRegion(GetDefaultCountry()); + ASSERT(region != DiscIO::Region::Unknown); + return region; + } + + switch (GetDefaultLanguage()) + { + case DiscIO::Language::Japanese: + return DiscIO::Region::NTSC_J; + case DiscIO::Language::Korean: + return DiscIO::Region::NTSC_K; + default: + return DiscIO::Region::PAL; + } +} + +DiscIO::Language GetDefaultLanguage() +{ + static const DiscIO::Language language = ComputeDefaultLanguage(); + return language; +} + +std::optional GetOptionalDefaultCountry() +{ + static const std::optional country = ComputeDefaultCountry(); + return country; +} + +u8 GetDefaultCountry() +{ + constexpr u8 FALLBACK_COUNTRY = 0x6c; // Switzerland, as per Dolphin tradition + return GetOptionalDefaultCountry().value_or(FALLBACK_COUNTRY); +} + +DiscIO::Region GetDefaultRegion() +{ + static const DiscIO::Region region = ComputeDefaultRegion(); + return region; +} + +} // namespace Config diff --git a/Source/Core/Core/Config/DefaultLocale.h b/Source/Core/Core/Config/DefaultLocale.h new file mode 100644 index 0000000000..cc4abfceee --- /dev/null +++ b/Source/Core/Core/Config/DefaultLocale.h @@ -0,0 +1,23 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" + +namespace DiscIO +{ +enum class Language; +enum class Region; +} // namespace DiscIO + +namespace Config +{ +DiscIO::Language GetDefaultLanguage(); +std::optional GetOptionalDefaultCountry(); +u8 GetDefaultCountry(); +DiscIO::Region GetDefaultRegion(); +} // namespace Config diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index d0fbd6c1f2..8fc3580891 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -8,6 +8,7 @@ #include "AudioCommon/AudioCommon.h" #include "Common/Config/Config.h" +#include "Core/Config/DefaultLocale.h" #include "Core/HW/EXI/EXI_Device.h" #include "Core/HW/Memmap.h" #include "Core/HW/SI/SI_Device.h" @@ -108,7 +109,7 @@ const Info MAIN_CUSTOM_RTC_ENABLE{{System::Main, "Core", "EnableCustomRTC" // Default to seconds between 1.1.1970 and 1.1.2000 const Info MAIN_CUSTOM_RTC_VALUE{{System::Main, "Core", "CustomRTCValue"}, 946684800}; const Info MAIN_FALLBACK_REGION{{System::Main, "Core", "FallbackRegion"}, - DiscIO::Region::NTSC_J}; + GetDefaultRegion()}; const Info MAIN_AUTO_DISC_CHANGE{{System::Main, "Core", "AutoDiscChange"}, false}; const Info MAIN_ALLOW_SD_WRITES{{System::Main, "Core", "WiiSDCardAllowWrites"}, true}; const Info MAIN_ENABLE_SAVESTATES{{System::Main, "Core", "EnableSaveStates"}, false}; diff --git a/Source/Core/Core/Config/SYSCONFSettings.cpp b/Source/Core/Core/Config/SYSCONFSettings.cpp index 3aaebc52bc..f1a21f3e73 100644 --- a/Source/Core/Core/Config/SYSCONFSettings.cpp +++ b/Source/Core/Core/Config/SYSCONFSettings.cpp @@ -4,13 +4,16 @@ #include "Core/Config/SYSCONFSettings.h" +#include "Core/Config/DefaultLocale.h" + namespace Config { // SYSCONF.IPL const Info SYSCONF_SCREENSAVER{{System::SYSCONF, "IPL", "SSV"}, false}; -const Info SYSCONF_LANGUAGE{{System::SYSCONF, "IPL", "LNG"}, 0x01}; -const Info SYSCONF_COUNTRY{{System::SYSCONF, "IPL", "SADR"}, 0x6c}; +const Info SYSCONF_LANGUAGE{{System::SYSCONF, "IPL", "LNG"}, + static_cast(GetDefaultLanguage())}; +const Info SYSCONF_COUNTRY{{System::SYSCONF, "IPL", "SADR"}, GetDefaultCountry()}; const Info SYSCONF_WIDESCREEN{{System::SYSCONF, "IPL", "AR"}, true}; const Info SYSCONF_PROGRESSIVE_SCAN{{System::SYSCONF, "IPL", "PGS"}, true}; const Info SYSCONF_PAL60{{System::SYSCONF, "IPL", "E60"}, 0x01}; diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index d4ec804b3a..91161cd4e9 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -33,6 +33,7 @@ #include "Core/Boot/Boot.h" #include "Core/CommonTitles.h" +#include "Core/Config/DefaultLocale.h" #include "Core/Config/MainSettings.h" #include "Core/Config/SYSCONFSettings.h" #include "Core/ConfigLoaders/GameConfigLoader.h" @@ -472,7 +473,8 @@ void SConfig::LoadCoreSettings(IniFile& ini) core->Get("CPUThread", &bCPUThread, true); core->Get("SyncOnSkipIdle", &bSyncGPUOnSkipIdleHack, true); core->Get("EnableCheats", &bEnableCheats, false); - core->Get("SelectedLanguage", &SelectedLanguage, 0); + core->Get("SelectedLanguage", &SelectedLanguage, + DiscIO::ToGameCubeLanguage(Config::GetDefaultLanguage())); core->Get("OverrideRegionSettings", &bOverrideRegionSettings, false); core->Get("DPL2Decoder", &bDPL2Decoder, false); core->Get("AudioLatency", &iLatency, 20); @@ -963,12 +965,11 @@ DiscIO::Region SConfig::GetFallbackRegion() DiscIO::Language SConfig::GetCurrentLanguage(bool wii) const { - int language_value; + DiscIO::Language language; if (wii) - language_value = Config::Get(Config::SYSCONF_LANGUAGE); + language = static_cast(Config::Get(Config::SYSCONF_LANGUAGE)); else - language_value = SConfig::GetInstance().SelectedLanguage + 1; - DiscIO::Language language = static_cast(language_value); + language = DiscIO::FromGameCubeLanguage(SConfig::GetInstance().SelectedLanguage); // Get rid of invalid values (probably doesn't matter, but might as well do it) if (language > DiscIO::Language::Unknown || language < DiscIO::Language::Japanese) diff --git a/Source/Core/Core/Host.h b/Source/Core/Core/Host.h index cf65c22eb0..ec539a01d2 100644 --- a/Source/Core/Core/Host.h +++ b/Source/Core/Core/Host.h @@ -5,6 +5,7 @@ #pragma once #include +#include // Host - defines an interface for the emulator core to communicate back to the // OS-specific layer @@ -32,9 +33,11 @@ enum class HostMessageID WMUserJobDispatch, }; +std::vector Host_GetPreferredLocales(); bool Host_UIBlocksControllerState(); bool Host_RendererHasFocus(); bool Host_RendererIsFullscreen(); + void Host_Message(HostMessageID id); void Host_NotifyMapLoaded(); void Host_RefreshDSPDebuggerWindow(); diff --git a/Source/Core/DiscIO/Enums.cpp b/Source/Core/DiscIO/Enums.cpp index c43132b7f2..7ee3ce78eb 100644 --- a/Source/Core/DiscIO/Enums.cpp +++ b/Source/Core/DiscIO/Enums.cpp @@ -126,6 +126,22 @@ bool IsNTSC(Region region) return region == Region::NTSC_J || region == Region::NTSC_U || region == Region::NTSC_K; } +int ToGameCubeLanguage(Language language) +{ + if (language < Language::English || language > Language::Dutch) + return 0; + else + return static_cast(language) - 1; +} + +Language FromGameCubeLanguage(int language) +{ + if (language < 0 || language > 5) + return Language::Unknown; + else + return static_cast(language + 1); +} + // Increment CACHE_REVISION (GameFileCache.cpp) if the code below is modified Country TypicalCountryForRegion(Region region) diff --git a/Source/Core/DiscIO/Enums.h b/Source/Core/DiscIO/Enums.h index 14940d92a8..18a344b680 100644 --- a/Source/Core/DiscIO/Enums.h +++ b/Source/Core/DiscIO/Enums.h @@ -76,6 +76,9 @@ bool IsDisc(Platform volume_type); bool IsWii(Platform volume_type); bool IsNTSC(Region region); +int ToGameCubeLanguage(Language language); +Language FromGameCubeLanguage(int language); + Country TypicalCountryForRegion(Region region); Region SysConfCountryToRegion(u8 country_code); // Avoid using this function if you can. Country codes aren't always reliable region indicators. diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 9d89aee8b9..0c9b412a00 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -166,6 +166,7 @@ + @@ -737,6 +738,7 @@ + diff --git a/Source/Core/DolphinNoGUI/MainNoGUI.cpp b/Source/Core/DolphinNoGUI/MainNoGUI.cpp index 5ab18a38eb..2c9d39a4b0 100644 --- a/Source/Core/DolphinNoGUI/MainNoGUI.cpp +++ b/Source/Core/DolphinNoGUI/MainNoGUI.cpp @@ -10,6 +10,8 @@ #include #include #include +#include + #ifndef _WIN32 #include #else @@ -48,9 +50,15 @@ static void signal_handler(int) s_platform->RequestShutdown(); } +std::vector Host_GetPreferredLocales() +{ + return {}; +} + void Host_NotifyMapLoaded() { } + void Host_RefreshDSPDebuggerWindow() { } diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp index f5202c1287..fdf0309b26 100644 --- a/Source/Core/DolphinQt/Host.cpp +++ b/Source/Core/DolphinQt/Host.cpp @@ -6,6 +6,7 @@ #include #include +#include #include @@ -95,6 +96,17 @@ void Host::ResizeSurface(int new_width, int new_height) g_renderer->ResizeSurface(); } +std::vector Host_GetPreferredLocales() +{ + const QStringList ui_languages = QLocale::system().uiLanguages(); + std::vector converted_languages(ui_languages.size()); + + for (int i = 0; i < ui_languages.size(); ++i) + converted_languages[i] = ui_languages[i].toStdString(); + + return converted_languages; +} + void Host_Message(HostMessageID id) { if (id == HostMessageID::WMUserStop) diff --git a/Source/DSPTool/StubHost.cpp b/Source/DSPTool/StubHost.cpp index b9393f1656..19c85a642b 100644 --- a/Source/DSPTool/StubHost.cpp +++ b/Source/DSPTool/StubHost.cpp @@ -6,9 +6,14 @@ // do nothing except return default values when required. #include +#include #include "Core/Host.h" +std::vector Host_GetPreferredLocales() +{ + return {}; +} void Host_NotifyMapLoaded() { } diff --git a/Source/UnitTests/StubHost.cpp b/Source/UnitTests/StubHost.cpp index 211ee8bfba..4b9db8f033 100644 --- a/Source/UnitTests/StubHost.cpp +++ b/Source/UnitTests/StubHost.cpp @@ -5,11 +5,15 @@ // Stub implementation of the Host_* callbacks for tests. These implementations // do nothing except return default values when required. -#include #include +#include #include "Core/Host.h" +std::vector Host_GetPreferredLocales() +{ + return {}; +} void Host_NotifyMapLoaded() { }