From 9141ff453b72dbf7cf2aa53ad46612ab67bc8cae Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Tue, 4 Aug 2026 09:04:29 -0700 Subject: [PATCH] lang: detect forced UTF-8 ACP, try to opt-out (#853) ## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change Fixes broken Japanese text when Windows `Use Unicode UTF-8 for worldwide language support` setting is enabled. This setting changes the system ACP to UTF-8 (65001), causing legacy Shift-JIS lead-byte checks to fail. Windows 11: requests the legacy process code page through the manifest. Windows 10: detects ACP 65001 (UTF-8), warns the user via deferred log, and applies compatibility hook (only for popn pika model for now) Windows 7 and below - UTF-8 option doesn't exist. ## Testing --- src/spice2x/build/manifest.manifest | 1 + src/spice2x/hooks/lang.cpp | 45 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/spice2x/build/manifest.manifest b/src/spice2x/build/manifest.manifest index f4d072b..d19d2e9 100644 --- a/src/spice2x/build/manifest.manifest +++ b/src/spice2x/build/manifest.manifest @@ -6,6 +6,7 @@ PerMonitorV2 true + Legacy diff --git a/src/spice2x/hooks/lang.cpp b/src/spice2x/hooks/lang.cpp index 99a4f32..553b966 100644 --- a/src/spice2x/hooks/lang.cpp +++ b/src/spice2x/hooks/lang.cpp @@ -13,7 +13,9 @@ #include "avs/game.h" #include "games/iidx/iidx.h" #include "games/gitadora/gitadora.h" +#include "games/popn/popn.h" #include "games/sdvx/sdvx.h" +#include "util/deferlog.h" #include "util/detour.h" #include "util/logging.h" #include "util/utils.h" @@ -29,6 +31,7 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr; #ifdef SPICE64 static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr; static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr; +static decltype(IsDBCSLeadByteEx) *IsDBCSLeadByteEx_orig = nullptr; static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr; static decltype(GetLocaleInfoA) *GetLocaleInfoA_orig = nullptr; static decltype(GetThreadLocale) *GetThreadLocale_orig = nullptr; @@ -182,9 +185,30 @@ static BOOL WINAPI IsDBCSLeadByte_hook ( BYTE TestChar ) { + if (IsDBCSLeadByteEx_orig) { + return IsDBCSLeadByteEx_orig(CODEPAGE_SHIFT_JIS, TestChar); + } + return IsDBCSLeadByteEx(CODEPAGE_SHIFT_JIS, TestChar); } +static BOOL WINAPI IsDBCSLeadByteEx_hook( + UINT CodePage, + BYTE TestChar) +{ + switch (CodePage) { + case CP_ACP: + case CP_THREAD_ACP: + CodePage = CODEPAGE_SHIFT_JIS; + break; + + default: + break; + } + + return IsDBCSLeadByteEx_orig(CodePage, TestChar); +} + static int WINAPI @@ -250,6 +274,18 @@ GetLocaleInfoA_hook( void hooks::lang::early_init() { log_info("hooks::lang", "early initialization"); + const auto native_code_page = GetACP(); + if (native_code_page == CP_UTF8) { + log_warning( + "hooks::lang", + "Windows is using UTF-8 as the system code page; " + "some games may render text incorrectly or behave unexpectedly"); + + deferredlogs::defer_error_messages({ + "Windows is using UTF-8 as the system code page", + " some games may render text incorrectly or behave unexpectedly"}); + } + // hooking these two functions fixes the jubeat mojibake detour::trampoline_try("kernel32.dll", "GetACP", GetACP_hook, &GetACP_orig); detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig); @@ -315,6 +351,15 @@ void hooks::lang::early_init() { WideCharToMultiByte_hook, &WideCharToMultiByte_orig); } + + if (games::popn::is_pikapika_model() && native_code_page == CP_UTF8) { + detour::trampoline_try( + "kernel32.dll", + "IsDBCSLeadByteEx", + IsDBCSLeadByteEx_hook, + &IsDBCSLeadByteEx_orig); + } + #endif }