From 9936088286f4e66791159ece72897964c2e46ad0 Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Sun, 12 Apr 2026 02:11:24 -0700 Subject: [PATCH] popn: subscreen overlay (#631) ## Link to GitHub Issue or related Pull Request, if one exists #618 ## Description of change Add overlay for subscreen. Add an option for no subscreen window. Use the same "force redraw subscreen" logic we used in SDVX to fix the same issue. Known issues: * still crashing when running windowed if there is only one monitor * having only one monitor causes sunscreen overlay to not work (NumberOfAdaptersInGroup issue?) ## Testing --- src/spice2x/CMakeLists.txt | 1 + src/spice2x/games/popn/popn.cpp | 34 +++++++++++- src/spice2x/games/popn/popn.h | 8 +++ .../graphics/backends/d3d9/d3d9_backend.cpp | 7 ++- src/spice2x/hooks/graphics/graphics.cpp | 12 ++++- src/spice2x/launcher/launcher.cpp | 4 ++ src/spice2x/launcher/options.cpp | 9 ++++ src/spice2x/launcher/options.h | 1 + src/spice2x/misc/wintouchemu.cpp | 5 ++ src/spice2x/overlay/overlay.cpp | 4 ++ src/spice2x/overlay/windows/exitprompt.cpp | 3 ++ src/spice2x/overlay/windows/popn_sub.cpp | 53 +++++++++++++++++++ src/spice2x/overlay/windows/popn_sub.h | 22 ++++++++ 13 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 src/spice2x/overlay/windows/popn_sub.cpp create mode 100644 src/spice2x/overlay/windows/popn_sub.h diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index 02e4d42b..e50cea1c 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -583,6 +583,7 @@ set(SOURCE_FILES ${SOURCE_FILES} overlay/windows/log.cpp overlay/windows/midi.cpp overlay/windows/patch_manager.cpp + overlay/windows/popn_sub.cpp overlay/windows/wnd_manager.cpp # rawinput diff --git a/src/spice2x/games/popn/popn.cpp b/src/spice2x/games/popn/popn.cpp index b90830d0..19f3b71f 100644 --- a/src/spice2x/games/popn/popn.cpp +++ b/src/spice2x/games/popn/popn.cpp @@ -3,6 +3,7 @@ #include "bi3a_hook.h" #include #include +#include "hooks/graphics/graphics.h" #include "rawinput/rawinput.h" #include "util/detour.h" #include "util/fileutils.h" @@ -17,11 +18,15 @@ #include "misc/eamuse.h" #include "util/sysutils.h" #include "io.h" +#include "util/deferlog.h" +#include "misc/wintouchemu.h" namespace games::popn { + bool SHOW_PIKA_MONITOR_WARNING = false; + #if SPICE64 && !SPICE_XP - + static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr; static @@ -406,6 +411,29 @@ namespace games::popn { #endif #if SPICE64 && !SPICE_XP + if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value()) { + SHOW_PIKA_MONITOR_WARNING = true; + log_warning( + "popn", + "\n\n" + "!!! using -dxmainadapter option is NOT recommended due to known !!!\n" + "!!! compatibility issues with the game !!!\n" + "!!! !!!\n" + "!!! * game may launch in wrong resolution or refresh rate !!!\n" + "!!! * touch / mouse input may stop working in subscreen / overlay !!!\n" + "!!! !!!\n" + "!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n" + "!!! option instead of -dxmainadapter !!!\n\n" + ); + + deferredlogs::defer_error_messages({ + "-dxmainadapter option is NOT recommended due to known compatibility ", + " issues with the game!", + " * game may launch in wrong resolution or refresh rate", + " * touch / mouse input may stop working in subscreen / overlay", + " use Change Main Monitor (-mainmonitor) option instead" + }); + } // monitor hook DisplayConfigGetDeviceInfo_orig = @@ -445,6 +473,10 @@ namespace games::popn { // 00000100 0000000B 00000001 (button 9) // set third column to 0 and it will work with BIO2 + wintouchemu::FORCE = true; + wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true; + wintouchemu::hook_title_ends("", "Main Screen", avs::game::DLL_INSTANCE); + #endif } diff --git a/src/spice2x/games/popn/popn.h b/src/spice2x/games/popn/popn.h index 99444423..e11918e8 100644 --- a/src/spice2x/games/popn/popn.h +++ b/src/spice2x/games/popn/popn.h @@ -6,9 +6,17 @@ namespace games::popn { static inline bool is_pikapika_model() { + +#if SPICE64 return (avs::game::is_model("M39") && avs::game::SPEC[0] == 'D'); +#else + return false; +#endif + } + extern bool SHOW_PIKA_MONITOR_WARNING; + class POPNGame : public games::Game { public: POPNGame(); diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp index 23e3246b..30883d56 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp @@ -661,7 +661,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT } // in windowed mode, LDJ will always launch two windows, no special handling needed here } else if (avs::game::is_model("KFC")) { - if (GRAPHICS_WINDOWED & GRAPHICS_PREVENT_SECONDARY_WINDOW) { + if (GRAPHICS_WINDOWED && GRAPHICS_PREVENT_SECONDARY_WINDOW) { // user wants windowed mode but does not want subscreen at all pCaps->NumberOfAdaptersInGroup = 1; } else { @@ -1206,7 +1206,10 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) { if (SUB_SWAP_CHAIN != nullptr) { wintouchemu::update(); - if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW) { + // newer versions of exceed gear needs SUBSCREEN_FORCE_REDRAW + // (when enabled on older versions of EG, you end up with graphical glitches on the subscreen + // early versions of popn HC needs this as well, otherwise the subscreen doesn't update at all + if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW || games::popn::is_pikapika_model()) { SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0); } } diff --git a/src/spice2x/hooks/graphics/graphics.cpp b/src/spice2x/hooks/graphics/graphics.cpp index 08c430cb..57a50b9a 100644 --- a/src/spice2x/hooks/graphics/graphics.cpp +++ b/src/spice2x/hooks/graphics/graphics.cpp @@ -16,6 +16,7 @@ #include "games/ddr/ddr.h" #include "games/gitadora/gitadora.h" #include "games/iidx/iidx.h" +#include "games/popn/popn.h" #include "hooks/graphics/backends/d3d9/d3d9_backend.h" #include "launcher/shutdown.h" #include "overlay/overlay.h" @@ -401,7 +402,9 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC if (is_popn_sub_window) { POPN_SUBSCREEN_WINDOW = result; - graphics_hook_subscreen_window(POPN_SUBSCREEN_WINDOW); + if (!GRAPHICS_PREVENT_SECONDARY_WINDOW) { + graphics_hook_subscreen_window(POPN_SUBSCREEN_WINDOW); + } } disable_touch_indicators(result); @@ -671,6 +674,13 @@ static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) { return true; } + if (games::popn::is_pikapika_model() && + GRAPHICS_PREVENT_SECONDARY_WINDOW && + hWnd == POPN_SUBSCREEN_WINDOW) { + log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd)); + return true; + } + // call original return ShowWindow_orig(hWnd, nCmdShow); } diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index dc23eb01..3eccc516 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -590,6 +590,10 @@ int main_implementation(int argc, char *argv[]) { if (options[launcher::Options::PopnMusicForceSDMode].value_bool()) { avs::ea3::PCB_TYPE = 0; } + if (options[launcher::Options::PopnNoSub].value_bool()) { + GRAPHICS_FORCE_SINGLE_ADAPTER = true; + GRAPHICS_PREVENT_SECONDARY_WINDOW = true; + } if (options[launcher::Options::LoadMetalGearArcadeModule].value_bool()) { attach_mga = true; } diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index bc932ce6..172952bf 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -988,6 +988,15 @@ static const std::vector OPTION_DEFINITIONS = { .game_name = "Pop'n Music", .category = "Game Options", }, + { + // PopnNoSub + .title = "Pop'n Music PikaPika Subscreen Disable", + .name = "popnnosub", + .desc = "Prevents PikaPika model subscreen from launching a separate window.", + .type = OptionType::Bool, + .game_name = "Pop'n Music", + .category = "Game Options", + }, { .title = "Force Load HELLO! Pop'n Music Module", .name = "hpm", diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index c2bec625..92e6388e 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -99,6 +99,7 @@ namespace launcher { LoadPopnMusicModule, PopnMusicForceHDMode, PopnMusicForceSDMode, + PopnNoSub, LoadHelloPopnMusicModule, LoadGitaDoraModule, GitaDoraTwoChannelAudio, diff --git a/src/spice2x/misc/wintouchemu.cpp b/src/spice2x/misc/wintouchemu.cpp index cb3ba5b3..944584d2 100644 --- a/src/spice2x/misc/wintouchemu.cpp +++ b/src/spice2x/misc/wintouchemu.cpp @@ -12,6 +12,7 @@ #include "cfg/screen_resize.h" #include "games/gitadora/gitadora.h" #include "games/iidx/iidx.h" +#include "games/popn/popn.h" #include "hooks/graphics/graphics.h" #include "overlay/overlay.h" #include "overlay/windows/generic_sub.h" @@ -405,6 +406,10 @@ namespace wintouchemu { // use mouse position as ImGui overlay will block the touch window log_info("wintouchemu", "use mouse cursor API for ldj overlay subscreen"); USE_MOUSE = true; + } else if (games::popn::is_pikapika_model()) { + // same as iidx case above + log_info("wintouchemu", "use mouse cursor API for popn overlay subscreen"); + USE_MOUSE = true; } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) { log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen"); USE_MOUSE = true; diff --git a/src/spice2x/overlay/overlay.cpp b/src/spice2x/overlay/overlay.cpp index a6a934f8..2b7c3099 100644 --- a/src/spice2x/overlay/overlay.cpp +++ b/src/spice2x/overlay/overlay.cpp @@ -5,6 +5,7 @@ #include "games/io.h" #include "games/gitadora/gitadora.h" #include "games/iidx/iidx.h" +#include "games/popn/popn.h" #include "hooks/graphics/graphics.h" #include "misc/eamuse.h" #include "touch/touch.h" @@ -35,6 +36,7 @@ #include "windows/iopanel_ddr.h" #include "windows/iopanel_gfdm.h" #include "windows/iopanel_iidx.h" +#include "windows/popn_sub.h" #include "windows/sdvx_sub.h" #include "windows/keypad.h" #include "windows/log.h" @@ -415,6 +417,8 @@ void overlay::SpiceOverlay::init() { window_sub = new overlay::windows::SDVXSubScreen(this); } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) { window_sub = new overlay::windows::GitaDoraSubScreen(this); + } else if (games::popn::is_pikapika_model()) { + window_sub = new overlay::windows::PopnSubScreen(this); } if (window_sub) { diff --git a/src/spice2x/overlay/windows/exitprompt.cpp b/src/spice2x/overlay/windows/exitprompt.cpp index 38d6a166..8b36e720 100644 --- a/src/spice2x/overlay/windows/exitprompt.cpp +++ b/src/spice2x/overlay/windows/exitprompt.cpp @@ -4,6 +4,7 @@ #include "util/logging.h" #include "games/iidx/iidx.h" #include "games/gitadora/gitadora.h" +#include "games/popn/popn.h" namespace overlay::windows { @@ -72,6 +73,8 @@ namespace overlay::windows { sub = "Show DRS Dance Floor"; } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) { sub = "Show GITADORA Subscreen"; + } else if (games::popn::is_pikapika_model()) { + sub = "Show Pop'n Subscreen"; } build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false); diff --git a/src/spice2x/overlay/windows/popn_sub.cpp b/src/spice2x/overlay/windows/popn_sub.cpp new file mode 100644 index 00000000..8bfaea71 --- /dev/null +++ b/src/spice2x/overlay/windows/popn_sub.cpp @@ -0,0 +1,53 @@ +#undef CINTERFACE + +#include "avs/game.h" +#include "popn_sub.h" +#include "games/popn/popn.h" +#include "hooks/graphics/graphics.h" +#include "touch/touch.h" + +namespace overlay::windows { + + PopnSubScreen::PopnSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) { + this->title = "Pop'n Subscreen"; + + if (!games::popn::is_pikapika_model()) { + this->disabled_message = "Game did not launch as Pikapika Pop-kun (invalid )!"; + } else if (games::popn::SHOW_PIKA_MONITOR_WARNING) { + this->disabled_message = "Subscreen overlay is not compatible with -dxmainadapter option, use -mainmonitor instead"; + } else if (GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOW) { + this->disabled_message = "Subscren overlay was not enabled in spicecfg. Use the second window (ALT+TAB)."; + } + + this->resize_callback = keep_16_by_10; + float size = 0.5f; + this->init_size = ImVec2( + ImGui::GetIO().DisplaySize.x * size, + (ImGui::GetIO().DisplaySize.x * size * 10 / 16) + ImGui::GetFrameHeight()); + + this->size_max = ImVec2( + ImGui::GetIO().DisplaySize.x - ImGui::GetFrameHeight() * 2, + ImGui::GetIO().DisplaySize.y - ImGui::GetFrameHeight() * 2); + + // middle / bottom + this->init_pos = ImVec2( + ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2, + ImGui::GetIO().DisplaySize.y - this->init_size.y - (ImGui::GetFrameHeight() / 2)); + } + + void PopnSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) { + if (!this->get_active()) { + return; + } + + if (GRAPHICS_WINDOWED) { + // Touch needs to be registered on global coords + *x_out = SPICETOUCH_TOUCH_X + xy_in.x * SPICETOUCH_TOUCH_WIDTH; + *y_out = SPICETOUCH_TOUCH_Y + xy_in.y * SPICETOUCH_TOUCH_HEIGHT; + } else { + // Fullscreen mode, scale to game coords + *x_out = xy_in.x * ImGui::GetIO().DisplaySize.x; + *y_out = xy_in.y * ImGui::GetIO().DisplaySize.y; + } + } +} diff --git a/src/spice2x/overlay/windows/popn_sub.h b/src/spice2x/overlay/windows/popn_sub.h new file mode 100644 index 00000000..2e0726f8 --- /dev/null +++ b/src/spice2x/overlay/windows/popn_sub.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "overlay/window.h" +#include "overlay/windows/generic_sub.h" + +namespace overlay::windows { + + class PopnSubScreen : public GenericSubScreen { + public: + PopnSubScreen(SpiceOverlay *overlay); + + protected: + void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) override; + + private: + static void keep_16_by_10(ImGuiSizeCallbackData* data) { + data->DesiredSize.y = (data->DesiredSize.x * 10.f / 16.f) + ImGui::GetFrameHeight(); + } + }; +}