Compare commits

...

3 Commits

Author SHA1 Message Date
bicarus b53447bed5 popn: fix subscreen redraw option causing graphical glitches (#854)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Old behavior: Forced redraw presented the subscreen every main frame,
even when the game already presented it, causing duplicate presents and
tearing in popn (was fine in sdvx)

New behavior: Forced redraw acts as a fallback, presenting only when the
game skips or fails a subscreen update.

## Testing
Popn - no more glitching
Nabla - no regression
2026-08-06 04:14:31 -07:00
bicarus 4959a58de3 iidx: don't hook legacy camera unless requested (#856)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #855 

## Description of change
Legacy camera hooks are specifically for IIDX 25/26 and they only
perform redirection of device discovery (emulates USB vendor/device ID
and USB port). It has very limited use since most cameras are
practically unusable in IIDX 25/26. Don't enable it, unless explicitly
requested by the user.

New truth table for `-iidxcabcams` :

| Mode | `-iidx` off (cab setup) | `-iidx` on |
|---|---|---|
| `auto` | Native cameras enabled (same as `on`) | Cameras disabled
(same as `off`) |
| `off` | Cameras disabled | Cameras disabled |
| `on` | Native cameras enabled | Native cameras enabled |
| `legacy` | Native cameras enabled | IIDX 25/26 discovery emulation
enabled |

## Testing
2026-08-06 03:44:52 -07:00
bicarus 9141ff453b 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
2026-08-04 09:04:29 -07:00
11 changed files with 141 additions and 43 deletions
+1
View File
@@ -6,6 +6,7 @@
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"> <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<dpiAware>true</dpiAware> <dpiAware>true</dpiAware>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">Legacy</activeCodePage>
</asmv3:windowsSettings> </asmv3:windowsSettings>
</asmv3:application> </asmv3:application>
<dependency> <dependency>
+4 -16
View File
@@ -58,7 +58,7 @@ namespace games::iidx {
// settings // settings
bool FLIP_CAMS = false; bool FLIP_CAMS = false;
std::optional<bool> DISABLE_CAMS; cab_camera_access_mode CAB_CAMERA_ACCESS = cab_camera_access_mode::automatic;
bool TDJ_CAMERA = false; bool TDJ_CAMERA = false;
bool TDJ_CAMERA_PREFER_16_9 = true; bool TDJ_CAMERA_PREFER_16_9 = true;
bool TDJ_MODE = false; bool TDJ_MODE = false;
@@ -424,10 +424,7 @@ namespace games::iidx {
"RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE); "RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE);
// check if cam hook should be enabled // check if cam hook should be enabled
if (!DISABLE_CAMS.has_value()) { if (CAB_CAMERA_ACCESS == cab_camera_access_mode::legacy) {
log_fatal("iidx", "assertion failure - DISABLE_CAMS not set during attach");
}
if (!DISABLE_CAMS.value()) {
init_legacy_camera_hook(FLIP_CAMS); init_legacy_camera_hook(FLIP_CAMS);
} }
@@ -454,20 +451,11 @@ namespace games::iidx {
SetEnvironmentVariable("SCREEN_MODE", SCREEN_MODE.value().c_str()); SetEnvironmentVariable("SCREEN_MODE", SCREEN_MODE.value().c_str());
} }
// check for cab camera access for the second time (first time was in launcher.cpp) // auto with iidx module means turn off camera (non-cab use)
// this time, we are inside -iidx module hook, which means the user is likely NOT on a cab if (CAB_CAMERA_ACCESS == cab_camera_access_mode::automatic) {
// therefore, start with cams OFF by default, and allow user to forcibly override to ON
if (!games::iidx::DISABLE_CAMS.has_value()) {
games::iidx::DISABLE_CAMS = true;
if (options->at(launcher::Options::IIDXCabCamAccess).is_active() &&
options->at(launcher::Options::IIDXCabCamAccess).value_text() == "on") {
games::iidx::DISABLE_CAMS = false;
}
if (games::iidx::DISABLE_CAMS.value()) {
log_misc("iidx", "CONNECT_CAMERA env var set to 0"); log_misc("iidx", "CONNECT_CAMERA env var set to 0");
SetEnvironmentVariable("CONNECT_CAMERA", "0"); SetEnvironmentVariable("CONNECT_CAMERA", "0");
} }
}
// windowed subscreen, enabled by default, unless turned off by user // windowed subscreen, enabled by default, unless turned off by user
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool() && TDJ_MODE) { if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool() && TDJ_MODE) {
+8 -1
View File
@@ -11,6 +11,13 @@
namespace games::iidx { namespace games::iidx {
enum class cab_camera_access_mode {
automatic,
off,
on,
legacy,
};
enum class iidx_aio_emulation_state { enum class iidx_aio_emulation_state {
unknown, unknown,
bi2a_com2, bi2a_com2,
@@ -20,7 +27,7 @@ namespace games::iidx {
// settings // settings
extern bool FLIP_CAMS; extern bool FLIP_CAMS;
extern std::optional<bool> DISABLE_CAMS; extern cab_camera_access_mode CAB_CAMERA_ACCESS;
extern bool TDJ_CAMERA; extern bool TDJ_CAMERA;
extern bool TDJ_CAMERA_PREFER_16_9; extern bool TDJ_CAMERA_PREFER_16_9;
extern std::optional<std::string> TDJ_CAMERA_OVERRIDE; extern std::optional<std::string> TDJ_CAMERA_OVERRIDE;
@@ -1,5 +1,6 @@
#include "d3d9_backend.h" #include "d3d9_backend.h"
#include <atomic>
#include <cassert> #include <cassert>
#include <memory> #include <memory>
#include <thread> #include <thread>
@@ -113,6 +114,12 @@ static Direct3DCreate9On12Ex_t Direct3DCreate9On12Ex_orig = nullptr;
static bool ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = false; static bool ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = false;
static IDirect3DSwapChain9 *SUB_SWAP_CHAIN = nullptr; static IDirect3DSwapChain9 *SUB_SWAP_CHAIN = nullptr;
// main and subscreen presents may occur on different threads.
static std::atomic_bool SUBSCREEN_PRESENTED_SINCE_LAST_MAIN = false;
// do not mistake the fallback Present below for a game-originated Present.
static thread_local bool SUBSCREEN_FORCE_REDRAW_IN_PROGRESS = false;
static void graphics_d3d9_ldj_init_sub_screen( static void graphics_d3d9_ldj_init_sub_screen(
IDirect3DDevice9Ex *device, IDirect3DDevice9Ex *device,
D3DPRESENT_PARAMETERS *present_params, D3DPRESENT_PARAMETERS *present_params,
@@ -1445,6 +1452,12 @@ IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen() {
return surface; return surface;
} }
void graphics_d3d9_notify_subscreen_present() {
if (SUBSCREEN_FORCE_REDRAW && !SUBSCREEN_FORCE_REDRAW_IN_PROGRESS) {
SUBSCREEN_PRESENTED_SINCE_LAST_MAIN.store(true, std::memory_order_relaxed);
}
}
static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) { static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
// iidx/sdvx // iidx/sdvx
int swapchain = 1; int swapchain = 1;
@@ -1472,8 +1485,16 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
// //
// early versions of popn HC needs this as well, but not on by default as it can cause // early versions of popn HC needs this as well, but not on by default as it can cause
// graphical glitches on some GPUs // graphical glitches on some GPUs
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW) { //
// treat forced redraw as a fallback so it does not duplicate a successful game present.
const bool force_redraw = SUBSCREEN_FORCE_REDRAW &&
!SUBSCREEN_PRESENTED_SINCE_LAST_MAIN.exchange(false, std::memory_order_relaxed);
if (GRAPHICS_WINDOWED || force_redraw) {
SUBSCREEN_FORCE_REDRAW_IN_PROGRESS = true;
SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0); SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0);
SUBSCREEN_FORCE_REDRAW_IN_PROGRESS = false;
} }
} }
} }
@@ -15,6 +15,8 @@ void graphics_d3d9_on_present(
IDirect3DDevice9 *device, IDirect3DDevice9 *device,
IDirect3DDevice9 *wrapped_device); IDirect3DDevice9 *wrapped_device);
void graphics_d3d9_notify_subscreen_present();
IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen(); IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen();
struct WrappedIDirect3D9 : IDirect3D9Ex { struct WrappedIDirect3D9 : IDirect3D9Ex {
@@ -134,6 +134,10 @@ ULONG STDMETHODCALLTYPE WrappedIDirect3DDevice9::Release() {
this->main_swapchain->Release(); this->main_swapchain->Release();
this->main_swapchain = nullptr; this->main_swapchain = nullptr;
} }
if (this->implicit_sub_swapchain) {
this->implicit_sub_swapchain->Release();
this->implicit_sub_swapchain = nullptr;
}
for (auto &sc : this->sub_swapchain) { for (auto &sc : this->sub_swapchain) {
if (sc) { if (sc) {
sc->Release(); sc->Release();
@@ -487,6 +491,24 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
fake_sub_swapchain[0]->AddRef(); fake_sub_swapchain[0]->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain[0]); *ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain[0]);
graphics_screens_register(iSwapChain);
return D3D_OK;
} else if (SUBSCREEN_FORCE_REDRAW) {
// store implicit sub swap chain
if (!implicit_sub_swapchain) {
IDirect3DSwapChain9 *real_swapchain = nullptr;
HRESULT ret = pReal->GetSwapChain(iSwapChain, &real_swapchain);
if (FAILED(ret)) {
return ret;
}
implicit_sub_swapchain = new WrappedIDirect3DSwapChain9(this, real_swapchain);
implicit_sub_swapchain->should_run_hooks = false;
}
implicit_sub_swapchain->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(implicit_sub_swapchain);
graphics_screens_register(iSwapChain); graphics_screens_register(iSwapChain);
return D3D_OK; return D3D_OK;
} }
@@ -262,6 +262,7 @@ struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
std::atomic_ulong refs = 1; std::atomic_ulong refs = 1;
WrappedIDirect3DSwapChain9 *main_swapchain = nullptr; WrappedIDirect3DSwapChain9 *main_swapchain = nullptr;
WrappedIDirect3DSwapChain9 *implicit_sub_swapchain = nullptr;
WrappedIDirect3DSwapChain9 *sub_swapchain[3] = { nullptr, nullptr, nullptr }; WrappedIDirect3DSwapChain9 *sub_swapchain[3] = { nullptr, nullptr, nullptr };
FakeIDirect3DSwapChain9 *fake_sub_swapchain[3] = { nullptr, nullptr, nullptr }; FakeIDirect3DSwapChain9 *fake_sub_swapchain[3] = { nullptr, nullptr, nullptr };
@@ -96,6 +96,12 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::Present(const RECT *pSourc
pDirtyRegion, pDirtyRegion,
dwFlags); dwFlags);
// a successful game subscreen present suppresses the next forced fallback present.
if (SUCCEEDED(result) &&
(this == pDev->implicit_sub_swapchain || this == pDev->sub_swapchain[0])) {
graphics_d3d9_notify_subscreen_present();
}
// Some drivers report S_PRESENT_MODE_CHANGED after Windows moves the portrait SMALL // Some drivers report S_PRESENT_MODE_CHANGED after Windows moves the portrait SMALL
// head into the requested exclusive mode, leaving both group heads black. Toggle SMALL // head into the requested exclusive mode, leaving both group heads black. Toggle SMALL
// through another supported mode and restore it once, then retry the interrupted MAIN // through another supported mode and restore it once, then retry the interrupted MAIN
+45
View File
@@ -13,7 +13,9 @@
#include "avs/game.h" #include "avs/game.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/popn/popn.h"
#include "games/sdvx/sdvx.h" #include "games/sdvx/sdvx.h"
#include "util/deferlog.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
@@ -29,6 +31,7 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
#ifdef SPICE64 #ifdef SPICE64
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr; static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr; static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
static decltype(IsDBCSLeadByteEx) *IsDBCSLeadByteEx_orig = nullptr;
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr; static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
static decltype(GetLocaleInfoA) *GetLocaleInfoA_orig = nullptr; static decltype(GetLocaleInfoA) *GetLocaleInfoA_orig = nullptr;
static decltype(GetThreadLocale) *GetThreadLocale_orig = nullptr; static decltype(GetThreadLocale) *GetThreadLocale_orig = nullptr;
@@ -182,9 +185,30 @@ static BOOL WINAPI IsDBCSLeadByte_hook (
BYTE TestChar BYTE TestChar
) )
{ {
if (IsDBCSLeadByteEx_orig) {
return IsDBCSLeadByteEx_orig(CODEPAGE_SHIFT_JIS, TestChar);
}
return IsDBCSLeadByteEx(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 static
int int
WINAPI WINAPI
@@ -250,6 +274,18 @@ GetLocaleInfoA_hook(
void hooks::lang::early_init() { void hooks::lang::early_init() {
log_info("hooks::lang", "early initialization"); 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 // hooking these two functions fixes the jubeat mojibake
detour::trampoline_try("kernel32.dll", "GetACP", GetACP_hook, &GetACP_orig); detour::trampoline_try("kernel32.dll", "GetACP", GetACP_hook, &GetACP_orig);
detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig); detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig);
@@ -315,6 +351,15 @@ void hooks::lang::early_init() {
WideCharToMultiByte_hook, WideCharToMultiByte_hook,
&WideCharToMultiByte_orig); &WideCharToMultiByte_orig);
} }
if (games::popn::is_pikapika_model() && native_code_page == CP_UTF8) {
detour::trampoline_try(
"kernel32.dll",
"IsDBCSLeadByteEx",
IsDBCSLeadByteEx_hook,
&IsDBCSLeadByteEx_orig);
}
#endif #endif
} }
+18 -17
View File
@@ -5,7 +5,6 @@
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
#include <assert.h>
#include <shlwapi.h> #include <shlwapi.h>
#include <windows.h> #include <windows.h>
#include <cfg/configurator.h> #include <cfg/configurator.h>
@@ -524,22 +523,25 @@ int main_implementation(int argc, char *argv[]) {
games::iidx::FLIP_CAMS = true; games::iidx::FLIP_CAMS = true;
} }
// IIDX CONNECT_CAMERA logic here for cases where user is running without -iidx module // Resolve the IIDX camera policy here so it also applies without the -iidx module.
// for now, we assume that user may be running on a cab const auto &cab_camera_access = options[launcher::Options::IIDXCabCamAccess];
// (games::iidx::DISABLE_CAMS starts out as false unless user overrides) if (cab_camera_access.is_active()) {
// we will check again in IIDX module with a different default const auto value = cab_camera_access.value_text();
assert(!games::iidx::DISABLE_CAMS.has_value()); if (value == "off") {
if (options[launcher::Options::IIDXDisableCameras].value_bool()) { games::iidx::CAB_CAMERA_ACCESS = games::iidx::cab_camera_access_mode::off;
games::iidx::DISABLE_CAMS = true; } else if (value == "on") {
games::iidx::CAB_CAMERA_ACCESS = games::iidx::cab_camera_access_mode::on;
} else if (value == "legacy") {
games::iidx::CAB_CAMERA_ACCESS = games::iidx::cab_camera_access_mode::legacy;
} }
if (options[launcher::Options::IIDXCabCamAccess].is_active() && }
options[launcher::Options::IIDXCabCamAccess].value_text() == "off") {
games::iidx::DISABLE_CAMS = true; if (options[launcher::Options::IIDXDisableCameras].value_bool()) {
games::iidx::CAB_CAMERA_ACCESS = games::iidx::cab_camera_access_mode::off;
} }
if (options[launcher::Options::IIDXCamHook].value_bool()) { if (options[launcher::Options::IIDXCamHook].value_bool()) {
games::iidx::TDJ_CAMERA = true; games::iidx::TDJ_CAMERA = true;
// Disable legacy behaviour to avoid conflict games::iidx::CAB_CAMERA_ACCESS = games::iidx::cab_camera_access_mode::off;
games::iidx::DISABLE_CAMS = true;
} }
// CONNECT_CAMERA env var will be set once logging is enabled // CONNECT_CAMERA env var will be set once logging is enabled
@@ -1717,14 +1719,13 @@ int main_implementation(int argc, char *argv[]) {
GRAPHICS_FS_ORIENTATION_SWAP = true; GRAPHICS_FS_ORIENTATION_SWAP = true;
} }
// apply an explicit off outside of the -iidx module so it also works on cabinets.
// for cab usage - set environment variables (outside of -iidx module) if (games::iidx::CAB_CAMERA_ACCESS == games::iidx::cab_camera_access_mode::off &&
if (games::iidx::DISABLE_CAMS.has_value() &&
games::iidx::DISABLE_CAMS.value() &&
!cfg::CONFIGURATOR_STANDALONE) { !cfg::CONFIGURATOR_STANDALONE) {
log_misc("launcher::iidx", "CONNECT_CAMERA env var set to 0"); log_misc("launcher::iidx", "CONNECT_CAMERA env var set to 0");
SetEnvironmentVariable("CONNECT_CAMERA", "0"); SetEnvironmentVariable("CONNECT_CAMERA", "0");
} }
if (games::iidx::SOUND_OUTPUT_DEVICE.has_value() && if (games::iidx::SOUND_OUTPUT_DEVICE.has_value() &&
games::iidx::SOUND_OUTPUT_DEVICE.value() != "auto" && games::iidx::SOUND_OUTPUT_DEVICE.value() != "auto" &&
!cfg::CONFIGURATOR_STANDALONE) { !cfg::CONFIGURATOR_STANDALONE) {
+10 -6
View File
@@ -640,17 +640,21 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
}, },
{ {
// IIDXCabCamAccess // IIDXCabCamAccess
.title = "IIDX Use Official AC Cams", .title = "IIDX Official AC Camera Access",
.name = "iidxcabcams", .name = "iidxcabcams",
.desc = "For IIDX25+, allow direct access to cameras from real arcade cabinets. " .desc = "Controls how the game accesses USB cameras for IIDX 25+.\n\n"
"Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto.", "auto (default): use [off] when the IIDX module is enabled; otherwise, use [on].\n\n"
"on: game discovers and directly accesses cameras; requires official cameras on correct USB ports.\n\n"
"legacy: for IIDX 25/26 only; allow camera access with emulated discovery.\n\n"
"off: prevent game from accessing USB cameras.",
.type = OptionType::Enum, .type = OptionType::Enum,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Cab Peripherals", .category = "Cab Peripherals",
.elements = { .elements = {
{"auto", ""}, {"auto", ""},
{"on", ""},
{"legacy", ""},
{"off", ""}, {"off", ""},
{"on", ""}
}, },
}, },
{ {
@@ -977,7 +981,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.display_name = "sdvxsubredraw", .display_name = "sdvxsubredraw",
.aliases= "sdvxsubredraw", .aliases= "sdvxsubredraw",
.desc = "Check if submonitor in fullscreen mode doesn't update every frame; " .desc = "Check if submonitor in fullscreen mode doesn't update every frame; "
"this option forces subscreen to redraw every frame.", "this option presents the subscreen when the game does not.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Advanced Game Options", .category = "Advanced Game Options",
@@ -1171,7 +1175,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.title = "Pop'n Music PikaPika Subscreen Force Redraw", .title = "Pop'n Music PikaPika Subscreen Force Redraw",
.name = "popnsubredraw", .name = "popnsubredraw",
.desc = "Check if submonitor in fullscreen mode appears stuck; " .desc = "Check if submonitor in fullscreen mode appears stuck; "
"this option forces subscreen to redraw every frame.", "this option presents the subscreen when the game does not.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Pop'n Music", .game_name = "Pop'n Music",
.category = "Advanced Game Options", .category = "Advanced Game Options",