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 663f8ea56e..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);
@@ -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()
@@ -817,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");
@@ -902,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];