From e825ffade7682079ffd800635aeb96cf57d8705a Mon Sep 17 00:00:00 2001 From: Silent Date: Wed, 5 Oct 2022 21:59:50 +0200 Subject: [PATCH 1/2] Platform.Win32: Migrate to modern locale functions and names --- src/openrct2/platform/Platform.Win32.cpp | 143 +++++++++-------------- 1 file changed, 56 insertions(+), 87 deletions(-) diff --git a/src/openrct2/platform/Platform.Win32.cpp b/src/openrct2/platform/Platform.Win32.cpp index 663f8ea56e..8d01dcbc88 100644 --- a/src/openrct2/platform/Platform.Win32.cpp +++ b/src/openrct2/platform/Platform.Win32.cpp @@ -590,109 +590,81 @@ namespace Platform uint16_t GetLocaleLanguage() { - CHAR langCode[4]; - - if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVLANGNAME, reinterpret_cast(&langCode), sizeof(langCode)) - == 0) + wchar_t langCode[LOCALE_NAME_MAX_LENGTH]; + if (GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, langCode, static_cast(std::size(langCode))) == 0) { return LANGUAGE_UNDEFINED; } - if (strcmp(langCode, "ENG") == 0) + const std::pair supportedLocales[] = { + { L"ar", /*LANGUAGE_ARABIC*/ LANGUAGE_UNDEFINED }, // Experimental, don't risk offering it by default yet + { L"ca", LANGUAGE_CATALAN }, + { L"zh-Hans", LANGUAGE_CHINESE_SIMPLIFIED }, // May not be accurate enough + { L"zh-Hant", LANGUAGE_CHINESE_TRADITIONAL }, // May not be accurate enough + { L"cs", LANGUAGE_CZECH }, + { L"da", LANGUAGE_DANISH }, + { L"de", LANGUAGE_GERMAN }, + { L"en-GB", LANGUAGE_ENGLISH_UK }, + { L"en-US", LANGUAGE_ENGLISH_US }, + { L"eo", LANGUAGE_ESPERANTO }, + { L"es", LANGUAGE_SPANISH }, + { L"fr", LANGUAGE_FRENCH }, + { L"it", LANGUAGE_ITALIAN }, + { L"ja", LANGUAGE_JAPANESE }, + { L"ko", LANGUAGE_KOREAN }, + { L"hu", LANGUAGE_HUNGARIAN }, + { L"nl", LANGUAGE_DUTCH }, + { L"no", LANGUAGE_NORWEGIAN }, + { L"pl", LANGUAGE_POLISH }, + { L"pt-BR", LANGUAGE_PORTUGUESE_BR }, + { L"ru", LANGUAGE_RUSSIAN }, + { L"fi", LANGUAGE_FINNISH }, + { L"sv", LANGUAGE_SWEDISH }, + { L"tr", LANGUAGE_TURKISH }, + { L"vi", LANGUAGE_VIETNAMESE }, + }; + static_assert( + std::size(supportedLocales) == LANGUAGE_COUNT - 1, "GetLocaleLanguage: List of languages does not match the enum!"); + + for (const auto& locale : supportedLocales) { - return LANGUAGE_ENGLISH_UK; - } - if (strcmp(langCode, "ENU") == 0) - { - return LANGUAGE_ENGLISH_US; - } - if (strcmp(langCode, "DEU") == 0) - { - return LANGUAGE_GERMAN; - } - if (strcmp(langCode, "NLD") == 0) - { - return LANGUAGE_DUTCH; - } - if (strcmp(langCode, "FRA") == 0) - { - return LANGUAGE_FRENCH; - } - if (strcmp(langCode, "HUN") == 0) - { - return LANGUAGE_HUNGARIAN; - } - if (strcmp(langCode, "PLK") == 0) - { - return LANGUAGE_POLISH; - } - if (strcmp(langCode, "ESP") == 0) - { - return LANGUAGE_SPANISH; - } - if (strcmp(langCode, "SVE") == 0) - { - return LANGUAGE_SWEDISH; - } - if (strcmp(langCode, "ITA") == 0) - { - return LANGUAGE_ITALIAN; - } - if (strcmp(langCode, "POR") == 0) - { - return LANGUAGE_PORTUGUESE_BR; - } - if (strcmp(langCode, "FIN") == 0) - { - return LANGUAGE_FINNISH; - } - if (strcmp(langCode, "NOR") == 0) - { - return LANGUAGE_NORWEGIAN; - } - if (strcmp(langCode, "DAN") == 0) - { - return LANGUAGE_DANISH; + if (wcsncmp(langCode, locale.first.data(), locale.first.length()) == 0) + { + return locale.second; + } } return LANGUAGE_UNDEFINED; } CurrencyType GetLocaleCurrency() { - CHAR currCode[4]; - if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SINTLSYMBOL, reinterpret_cast(&currCode), sizeof(currCode)) == 0) + wchar_t currCode[9]; + if (GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SINTLSYMBOL, currCode, static_cast(std::size(currCode))) == 0) { return Platform::GetCurrencyValue(nullptr); } - return Platform::GetCurrencyValue(currCode); + return Platform::GetCurrencyValue(String::ToUtf8(currCode).c_str()); } MeasurementFormat GetLocaleMeasurementFormat() { UINT measurement_system; - if (GetLocaleInfo( - LOCALE_USER_DEFAULT, LOCALE_IMEASURE | LOCALE_RETURN_NUMBER, reinterpret_cast(&measurement_system), - sizeof(measurement_system)) + if (GetLocaleInfoEx( + LOCALE_NAME_USER_DEFAULT, LOCALE_IMEASURE | LOCALE_RETURN_NUMBER, reinterpret_cast(&measurement_system), + sizeof(measurement_system) / sizeof(wchar_t)) == 0) { return MeasurementFormat::Metric; } - switch (measurement_system) - { - case 1: - return MeasurementFormat::Imperial; - case 0: - default: - return MeasurementFormat::Metric; - } + return measurement_system == 1 ? MeasurementFormat::Imperial : MeasurementFormat::Metric; } uint8_t GetLocaleDateFormat() { // Retrieve short date format, eg "MM/dd/yyyy" - wchar_t dateFormat[20]; + wchar_t dateFormat[80]; if (GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SSHORTDATE, dateFormat, static_cast(std::size(dateFormat))) == 0) { @@ -705,8 +677,8 @@ namespace Platform // in our date formats. // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317787(v=vs.85).aspx // - wchar_t first[sizeof(dateFormat)]; - wchar_t second[sizeof(dateFormat)]; + wchar_t first[std::size(dateFormat)]; + wchar_t second[std::size(dateFormat)]; if (swscanf_s( dateFormat, L"%l[dyM]%*l[^dyM]%l[dyM]%*l[^dyM]%*l[dyM]", first, static_cast(std::size(first)), second, static_cast(std::size(second))) @@ -715,17 +687,17 @@ namespace Platform return DATE_FORMAT_DAY_MONTH_YEAR; } - if (wcsncmp(L"d", first, 1) == 0) + if (first[0] == L'd') { return DATE_FORMAT_DAY_MONTH_YEAR; } - if (wcsncmp(L"M", first, 1) == 0) + if (first[0] == L'M') { return DATE_FORMAT_MONTH_DAY_YEAR; } - if (wcsncmp(L"y", first, 1) == 0) + if (first[0] == L'y') { - if (wcsncmp(L"d", second, 1) == 0) + if (second[0] == 'd') { return DATE_FORMAT_YEAR_DAY_MONTH; } @@ -742,21 +714,18 @@ namespace Platform { UINT fahrenheit; - // GetLocaleInfo will set fahrenheit to 1 if the locale on this computer + // GetLocaleInfoEx will set fahrenheit to 1 if the locale on this computer // uses the United States measurement system or 0 otherwise. - if (GetLocaleInfo( - LOCALE_USER_DEFAULT, LOCALE_IMEASURE | LOCALE_RETURN_NUMBER, reinterpret_cast(&fahrenheit), - sizeof(fahrenheit)) + if (GetLocaleInfoEx( + LOCALE_NAME_USER_DEFAULT, LOCALE_IMEASURE | LOCALE_RETURN_NUMBER, reinterpret_cast(&fahrenheit), + sizeof(fahrenheit) / sizeof(wchar_t)) == 0) { // Assume celsius by default if function call fails return TemperatureUnit::Celsius; } - if (fahrenheit) - return TemperatureUnit::Fahrenheit; - - return TemperatureUnit::Celsius; + return fahrenheit == 1 ? TemperatureUnit::Fahrenheit : TemperatureUnit::Celsius; } bool ProcessIsElevated() From 0f978fe9073734201daba86c45dcfe2642c44b02 Mon Sep 17 00:00:00 2001 From: Silent Date: Wed, 5 Oct 2022 23:47:07 +0200 Subject: [PATCH 2/2] Remove the last non-Unicode Windows functions --- CMakeLists_mingw.txt | 3 +++ openrct2.common.props | 2 +- src/openrct2-ui/UiContext.Win32.cpp | 8 ++++---- src/openrct2/core/Guard.cpp | 13 +++++++------ src/openrct2/platform/Crash.cpp | 10 +++++----- src/openrct2/platform/Platform.Win32.cpp | 19 ++++++++----------- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/CMakeLists_mingw.txt b/CMakeLists_mingw.txt index ffb338d416..830c5031e4 100644 --- a/CMakeLists_mingw.txt +++ b/CMakeLists_mingw.txt @@ -23,3 +23,6 @@ SET(CMAKE_FIND_ROOT_PATH ${TARGET_ENVIRONMENT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +# Enable Unicode +add_compile_options(-municode) diff --git a/openrct2.common.props b/openrct2.common.props index fd137d0d7f..6afb7deebf 100644 --- a/openrct2.common.props +++ b/openrct2.common.props @@ -10,7 +10,7 @@ $(DefaultPlatformToolset) 10.0.17763.0 - MultiByte + Unicode $(SolutionDir)bin\ $(SolutionDir)obj\$(ProjectName)\$(Configuration)_$(Platform)\ diff --git a/src/openrct2-ui/UiContext.Win32.cpp b/src/openrct2-ui/UiContext.Win32.cpp index 14e7d70cda..740820d57c 100644 --- a/src/openrct2-ui/UiContext.Win32.cpp +++ b/src/openrct2-ui/UiContext.Win32.cpp @@ -62,20 +62,20 @@ namespace OpenRCT2::Ui public: Win32Context() { - _win32module = GetModuleHandleA(nullptr); + _win32module = GetModuleHandle(nullptr); } void SetWindowIcon(SDL_Window* window) override { if (_win32module != nullptr) { - HICON icon = LoadIconA(_win32module, MAKEINTRESOURCEA(IDI_ICON)); + HICON icon = LoadIcon(_win32module, MAKEINTRESOURCE(IDI_ICON)); if (icon != nullptr) { HWND hwnd = GetHWND(window); if (hwnd != nullptr) { - SendMessageA(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast(icon)); + SendMessage(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast(icon)); } } } @@ -83,7 +83,7 @@ namespace OpenRCT2::Ui bool IsSteamOverlayAttached() override { - return (GetModuleHandleA("GameOverlayRenderer.dll") != nullptr); + return (GetModuleHandleW(L"GameOverlayRenderer.dll") != nullptr); } void ShowMessageBox(SDL_Window* window, const std::string& message) override diff --git a/src/openrct2/core/Guard.cpp b/src/openrct2/core/Guard.cpp index 8a48f0f1ea..fdeefaf688 100644 --- a/src/openrct2/core/Guard.cpp +++ b/src/openrct2/core/Guard.cpp @@ -48,7 +48,7 @@ namespace Guard static std::optional _lastAssertMessage = std::nullopt; #ifdef _WIN32 - [[nodiscard]] static std::string CreateDialogAssertMessage(std::string_view); + [[nodiscard]] static std::wstring CreateDialogAssertMessage(std::string_view); static void ForceCrash(); #endif @@ -104,7 +104,8 @@ namespace Guard { // Show message box if we are not building for testing auto buffer = CreateDialogAssertMessage(formattedMessage); - int32_t result = MessageBoxA(nullptr, buffer.c_str(), OPENRCT2_NAME, MB_ABORTRETRYIGNORE | MB_ICONEXCLAMATION); + int32_t result = MessageBoxW( + nullptr, buffer.c_str(), L"" OPENRCT2_NAME, MB_ABORTRETRYIGNORE | MB_ICONEXCLAMATION); if (result == IDABORT) { ForceCrash(); @@ -134,19 +135,19 @@ namespace Guard } #ifdef _WIN32 - [[nodiscard]] static std::string CreateDialogAssertMessage(std::string_view formattedMessage) + [[nodiscard]] static std::wstring CreateDialogAssertMessage(std::string_view formattedMessage) { StringBuilder sb; sb.Append(ASSERTION_MESSAGE); - sb.Append("\r\n\r\n"); + sb.Append("\n\n"); sb.Append("Version: "); sb.Append(gVersionInfoFull); if (!formattedMessage.empty()) { - sb.Append("\r\n"); + sb.Append("\n"); sb.Append(formattedMessage); } - return sb.GetStdString(); + return String::ToWideChar({ sb.GetBuffer(), sb.GetLength() }); } static void ForceCrash() diff --git a/src/openrct2/platform/Crash.cpp b/src/openrct2/platform/Crash.cpp index 4d9e356edb..a47c4cc5a4 100644 --- a/src/openrct2/platform/Crash.cpp +++ b/src/openrct2/platform/Crash.cpp @@ -104,13 +104,13 @@ static bool OnCrash( { if (!succeeded) { - constexpr const char* DumpFailedMessage = "Failed to create the dump. Please file an issue with OpenRCT2 on GitHub and " - "provide latest save, and provide " - "information about what you did before the crash occurred."; - printf("%s\n", DumpFailedMessage); + constexpr const wchar_t* DumpFailedMessage = L"Failed to create the dump. Please file an issue with OpenRCT2 on GitHub " + L"and provide latest save, and provide information about what you did " + L"before the crash occurred."; + wprintf(L"%ls\n", DumpFailedMessage); if (!gOpenRCT2SilentBreakpad) { - MessageBoxA(nullptr, DumpFailedMessage, OPENRCT2_NAME, MB_OK | MB_ICONERROR); + MessageBoxW(nullptr, DumpFailedMessage, L"" OPENRCT2_NAME, MB_OK | MB_ICONERROR); } return succeeded; } diff --git a/src/openrct2/platform/Platform.Win32.cpp b/src/openrct2/platform/Platform.Win32.cpp index 8d01dcbc88..5056906d1f 100644 --- a/src/openrct2/platform/Platform.Win32.cpp +++ b/src/openrct2/platform/Platform.Win32.cpp @@ -45,7 +45,7 @@ static uint32_t _frequency = 0; static LARGE_INTEGER _entryTimestamp; // The name of the mutex used to prevent multiple instances of the game from running -static constexpr char SINGLE_INSTANCE_MUTEX_NAME[] = "RollerCoaster Tycoon 2_GSKMUTEX"; +static constexpr wchar_t SINGLE_INSTANCE_MUTEX_NAME[] = L"RollerCoaster Tycoon 2_GSKMUTEX"; # define SOFTWARE_CLASSES L"Software\\Classes" # define MUI_CACHE L"Local Settings\\Software\\Microsoft\\Windows\\Shell\\MuiCache" @@ -224,7 +224,7 @@ namespace Platform bool IsOSVersionAtLeast(uint32_t major, uint32_t minor, uint32_t build) { bool result = false; - auto hModule = GetModuleHandleA("ntdll.dll"); + auto hModule = GetModuleHandleW(L"ntdll.dll"); if (hModule != nullptr) { using RtlGetVersionPtr = long(WINAPI*)(PRTL_OSVERSIONINFOW); @@ -470,7 +470,7 @@ namespace Platform if (RegOpenKeyW(HKEY_CURRENT_USER, SOFTWARE_CLASSES, &hRootKey) == ERROR_SUCCESS) { // [hRootKey\.ext] - RegDeleteTreeA(hRootKey, extension); + RegDeleteTreeW(hRootKey, String::ToWideChar(extension).c_str()); // [hRootKey\OpenRCT2.ext] auto progIdName = get_progIdName(extension); @@ -786,18 +786,15 @@ namespace Platform bool EnsureDirectoryExists(u8string_view path) { - if (Path::DirectoryExists(path)) - return 1; - auto wPath = String::ToWideChar(path); auto success = CreateDirectoryW(wPath.c_str(), nullptr); - return success != FALSE; + return success != FALSE || GetLastError() == ERROR_ALREADY_EXISTS; } bool LockSingleInstance() { // Check if operating system mutex exists - HANDLE mutex = CreateMutex(nullptr, FALSE, SINGLE_INSTANCE_MUTEX_NAME); + HANDLE mutex = CreateMutexW(nullptr, FALSE, SINGLE_INSTANCE_MUTEX_NAME); if (mutex == nullptr) { log_error("unable to create mutex"); @@ -871,11 +868,11 @@ namespace Platform { // [hRootKey\openrct2] HKEY hClassKey; - if (RegCreateKeyA(hRootKey, "openrct2", &hClassKey) == ERROR_SUCCESS) + if (RegCreateKeyW(hRootKey, L"openrct2", &hClassKey) == ERROR_SUCCESS) { - if (RegSetValueA(hClassKey, nullptr, REG_SZ, "URL:openrct2", 0) == ERROR_SUCCESS) + if (RegSetValueW(hClassKey, nullptr, REG_SZ, L"URL:openrct2", 0) == ERROR_SUCCESS) { - if (RegSetKeyValueA(hClassKey, nullptr, "URL Protocol", REG_SZ, "", 0) == ERROR_SUCCESS) + if (RegSetKeyValueW(hClassKey, nullptr, L"URL Protocol", REG_SZ, "", 0) == ERROR_SUCCESS) { // [hRootKey\openrct2\shell\open\command] wchar_t exePath[MAX_PATH];