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 02e4d42..e50cea1 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 b90830d..19f3b71 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 9944442..e11918e 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 23e3246..30883d5 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 08c430c..57a50b9 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 dc23eb0..3eccc51 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 bc932ce..172952b 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 c2bec62..92e6388 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 cb3ba5b..944584d 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 a6a934f..2b7c309 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 38d6a16..8b36e72 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 0000000..8bfaea7 --- /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 0000000..2e0726f --- /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(); + } + }; +}