mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
281 lines
7.5 KiB
C++
281 lines
7.5 KiB
C++
#include "lang.h"
|
|
|
|
#define WIN32_NO_STATUS
|
|
#include <windows.h>
|
|
#undef WIN32_NO_STATUS
|
|
|
|
#include <winternl.h>
|
|
#include <ntstatus.h>
|
|
|
|
#include "avs/game.h"
|
|
#include "games/iidx/iidx.h"
|
|
#include "games/gitadora/gitadora.h"
|
|
#include "games/sdvx/sdvx.h"
|
|
#include "util/detour.h"
|
|
#include "util/logging.h"
|
|
#include "util/utils.h"
|
|
|
|
// ANSI/OEM Japanese; Japanese (Shift-JIS)
|
|
constexpr UINT CODEPAGE_SHIFT_JIS = 932;
|
|
|
|
static decltype(GetACP) *GetACP_orig = nullptr;
|
|
static decltype(GetOEMCP) *GetOEMCP_orig = nullptr;
|
|
static decltype(MultiByteToWideChar) *MultiByteToWideChar_orig = nullptr;
|
|
static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
|
|
|
|
#ifdef SPICE64
|
|
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
|
|
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
|
|
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
|
|
#endif
|
|
|
|
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
|
|
PWCH UnicodeString,
|
|
ULONG MaxBytesInUnicodeString,
|
|
PULONG BytesInUnicodeString,
|
|
const CHAR *MultiByteString,
|
|
ULONG BytesInMultiByteString)
|
|
{
|
|
// try to convert
|
|
auto wc_num = MultiByteToWideChar(
|
|
CODEPAGE_SHIFT_JIS,
|
|
0,
|
|
MultiByteString,
|
|
static_cast<int>(BytesInMultiByteString),
|
|
UnicodeString,
|
|
static_cast<int>(MaxBytesInUnicodeString)
|
|
);
|
|
|
|
// error handling
|
|
if (!wc_num) {
|
|
auto error = GetLastError();
|
|
|
|
switch (error) {
|
|
case ERROR_INSUFFICIENT_BUFFER:
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
|
|
case ERROR_INVALID_PARAMETER:
|
|
case ERROR_INVALID_FLAGS:
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
case ERROR_NO_UNICODE_TRANSLATION:
|
|
return STATUS_UNMAPPABLE_CHARACTER;
|
|
|
|
default:
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
|
|
// set byte count
|
|
if (BytesInUnicodeString) {
|
|
*BytesInUnicodeString = 2 * static_cast<UINT>(wc_num);
|
|
}
|
|
|
|
// return success
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static UINT WINAPI GetACP_hook() {
|
|
return CODEPAGE_SHIFT_JIS;
|
|
}
|
|
|
|
static UINT WINAPI GetOEMCP_hook() {
|
|
return CODEPAGE_SHIFT_JIS;
|
|
}
|
|
|
|
#ifdef SPICE64
|
|
|
|
static LCID WINAPI GetSystemDefaultLCID_hook() {
|
|
// ja-JP per https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c
|
|
// this is passed to LCMapStringA for kana conversions (e.g., for subscreen song search)
|
|
return 0x411;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int WINAPI MultiByteToWideChar_hook(
|
|
UINT CodePage,
|
|
DWORD dwFlags,
|
|
LPCCH lpMultiByteStr,
|
|
int cbMultiByte,
|
|
LPWSTR lpWideCharStr,
|
|
int cchWideChar)
|
|
{
|
|
switch (CodePage) {
|
|
case CP_ACP:
|
|
case CP_THREAD_ACP:
|
|
|
|
// this fixes pop'n music's mojibake issue with the system locale not set to Japanese
|
|
SetThreadLocale(MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN));
|
|
|
|
CodePage = CODEPAGE_SHIFT_JIS;
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return MultiByteToWideChar_orig(
|
|
CodePage,
|
|
dwFlags,
|
|
lpMultiByteStr,
|
|
cbMultiByte,
|
|
lpWideCharStr,
|
|
cchWideChar);
|
|
}
|
|
|
|
static int WINAPI GetLocaleInfoEx_hook (
|
|
LPCWSTR lpLocaleName,
|
|
LCTYPE LCType,
|
|
LPWSTR lpLCData,
|
|
int cchData)
|
|
{
|
|
|
|
#if 0
|
|
if (lpLocaleName == LOCALE_NAME_INVARIANT) {
|
|
log_misc("hooks::lang", "GetLocaleInfoEx_hook hit (LOCALE_NAME_INVARIANT), {}, {}", LCType, cchData);
|
|
} else if (lpLocaleName == LOCALE_NAME_SYSTEM_DEFAULT) {
|
|
log_misc("hooks::lang", "GetLocaleInfoEx_hook hit (LOCALE_NAME_SYSTEM_DEFAULT), {}, {}", LCType, cchData);
|
|
} else if (lpLocaleName == LOCALE_NAME_USER_DEFAULT) {
|
|
log_misc("hooks::lang", "GetLocaleInfoEx_hook hit (LOCALE_NAME_USER_DEFAULT), {}, {}", LCType, cchData);
|
|
} else {
|
|
log_misc("hooks::lang", "GetLocaleInfoEx_hook hit ({}), {}, {}", ws2s(lpLocaleName), LCType, cchData);
|
|
}
|
|
#endif
|
|
|
|
if (lpLocaleName == LOCALE_NAME_USER_DEFAULT &&
|
|
LCType == LOCALE_SISO639LANGNAME &&
|
|
lpLCData != NULL &&
|
|
cchData >= 3) {
|
|
log_misc("hooks::lang",
|
|
"GetLocaleInfoEx_hook hit (LOCALE_NAME_USER_DEFAULT / LOCALE_SISO639LANGNAME), return `ja`");
|
|
wcscpy(lpLCData, L"ja");
|
|
return 3;
|
|
}
|
|
|
|
if (lpLocaleName == LOCALE_NAME_USER_DEFAULT &&
|
|
LCType == LOCALE_SISO3166CTRYNAME &&
|
|
lpLCData != NULL &&
|
|
cchData >= 3) {
|
|
log_misc("hooks::lang",
|
|
"GetLocaleInfoEx_hook hit (LOCALE_NAME_USER_DEFAULT / LOCALE_SISO3166CTRYNAME), return `JP`");
|
|
wcscpy(lpLCData, L"JP");
|
|
return 3;
|
|
}
|
|
|
|
return GetLocaleInfoEx_orig(lpLocaleName, LCType, lpLCData, cchData);
|
|
}
|
|
|
|
#ifdef SPICE64
|
|
|
|
static BOOL WINAPI IsDBCSLeadByte_hook (
|
|
BYTE TestChar
|
|
)
|
|
{
|
|
return IsDBCSLeadByteEx(CODEPAGE_SHIFT_JIS, TestChar);
|
|
}
|
|
|
|
static
|
|
int
|
|
WINAPI
|
|
WideCharToMultiByte_hook(
|
|
UINT CodePage,
|
|
DWORD dwFlags,
|
|
LPCWCH lpWideCharStr,
|
|
int cchWideChar,
|
|
LPSTR lpMultiByteStr,
|
|
int cbMultiByte,
|
|
LPCCH lpDefaultChar,
|
|
LPBOOL lpUsedDefaultChar
|
|
)
|
|
{
|
|
switch (CodePage) {
|
|
case CP_ACP:
|
|
case CP_THREAD_ACP:
|
|
SetThreadLocale(MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN));
|
|
CodePage = CODEPAGE_SHIFT_JIS;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return WideCharToMultiByte_orig(
|
|
CodePage,
|
|
dwFlags,
|
|
lpWideCharStr,
|
|
cchWideChar,
|
|
lpMultiByteStr,
|
|
cbMultiByte,
|
|
lpDefaultChar,
|
|
lpUsedDefaultChar);
|
|
}
|
|
|
|
#endif
|
|
|
|
void hooks::lang::early_init() {
|
|
log_info("hooks::lang", "early initialization");
|
|
|
|
// 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);
|
|
|
|
#ifdef SPICE64 // SDVX5+ specific code
|
|
if (games::sdvx::is_valkyrie_model()) {
|
|
log_info("hooks::lang", "hooking GetSystemDefaultLCID");
|
|
detour::trampoline_try(
|
|
"kernel32.dll",
|
|
"GetSystemDefaultLCID",
|
|
GetSystemDefaultLCID_hook,
|
|
&GetSystemDefaultLCID_orig);
|
|
}
|
|
#endif
|
|
|
|
if (avs::game::is_model("XIF")) {
|
|
log_info("hooks::lang", "hooking GetLocaleInfoEx");
|
|
detour::trampoline_try(
|
|
"kernel32.dll",
|
|
"GetLocaleInfoEx",
|
|
GetLocaleInfoEx_hook,
|
|
&GetLocaleInfoEx_orig);
|
|
}
|
|
|
|
#ifdef SPICE64
|
|
// for TDJ subscreen search keyboard
|
|
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
|
|
log_info("hooks::lang", "hooking IsDBCSLeadByte");
|
|
detour::trampoline_try(
|
|
"kernel32.dll",
|
|
"IsDBCSLeadByte",
|
|
IsDBCSLeadByte_hook,
|
|
&IsDBCSLeadByte_orig);
|
|
}
|
|
|
|
if (games::gitadora::is_arena_model()) {
|
|
log_info("hooks::lang", "hooking WideCharToMultiByte");
|
|
detour::trampoline_try(
|
|
"kernel32.dll",
|
|
"WideCharToMultiByte",
|
|
WideCharToMultiByte_hook,
|
|
&WideCharToMultiByte_orig);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
void hooks::lang::init() {
|
|
log_info("hooks::lang", "initializing");
|
|
|
|
detour::iat_try("RtlMultiByteToUnicodeN", RtlMultiByteToUnicodeN_hook, nullptr, "ntdll.dll");
|
|
|
|
MultiByteToWideChar_orig = detour::iat_try(
|
|
"MultiByteToWideChar",
|
|
MultiByteToWideChar_hook,
|
|
nullptr,
|
|
"kernel32.dll");
|
|
}
|
|
|
|
bool hooks::lang::is_native_shiftjis() {
|
|
return GetACP() == CODEPAGE_SHIFT_JIS;
|
|
}
|