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
This commit is contained in:
bicarus
2026-04-12 02:11:24 -07:00
committed by GitHub
parent 4d8a6c1952
commit 9936088286
13 changed files with 159 additions and 4 deletions
+1
View File
@@ -583,6 +583,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/log.cpp overlay/windows/log.cpp
overlay/windows/midi.cpp overlay/windows/midi.cpp
overlay/windows/patch_manager.cpp overlay/windows/patch_manager.cpp
overlay/windows/popn_sub.cpp
overlay/windows/wnd_manager.cpp overlay/windows/wnd_manager.cpp
# rawinput # rawinput
+33 -1
View File
@@ -3,6 +3,7 @@
#include "bi3a_hook.h" #include "bi3a_hook.h"
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include "hooks/graphics/graphics.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/fileutils.h" #include "util/fileutils.h"
@@ -17,11 +18,15 @@
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/sysutils.h" #include "util/sysutils.h"
#include "io.h" #include "io.h"
#include "util/deferlog.h"
#include "misc/wintouchemu.h"
namespace games::popn { namespace games::popn {
bool SHOW_PIKA_MONITOR_WARNING = false;
#if SPICE64 && !SPICE_XP #if SPICE64 && !SPICE_XP
static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr; static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr;
static static
@@ -406,6 +411,29 @@ namespace games::popn {
#endif #endif
#if SPICE64 && !SPICE_XP #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 // monitor hook
DisplayConfigGetDeviceInfo_orig = DisplayConfigGetDeviceInfo_orig =
@@ -445,6 +473,10 @@ namespace games::popn {
// 00000100 0000000B 00000001 (button 9) // 00000100 0000000B 00000001 (button 9)
// set third column to 0 and it will work with BIO2 // 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 #endif
} }
+8
View File
@@ -6,9 +6,17 @@
namespace games::popn { namespace games::popn {
static inline bool is_pikapika_model() { static inline bool is_pikapika_model() {
#if SPICE64
return (avs::game::is_model("M39") && avs::game::SPEC[0] == 'D'); 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 { class POPNGame : public games::Game {
public: public:
POPNGame(); POPNGame();
@@ -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 // in windowed mode, LDJ will always launch two windows, no special handling needed here
} else if (avs::game::is_model("KFC")) { } 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 // user wants windowed mode but does not want subscreen at all
pCaps->NumberOfAdaptersInGroup = 1; pCaps->NumberOfAdaptersInGroup = 1;
} else { } else {
@@ -1206,7 +1206,10 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
if (SUB_SWAP_CHAIN != nullptr) { if (SUB_SWAP_CHAIN != nullptr) {
wintouchemu::update(); 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); SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0);
} }
} }
+11 -1
View File
@@ -16,6 +16,7 @@
#include "games/ddr/ddr.h" #include "games/ddr/ddr.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h" #include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "launcher/shutdown.h" #include "launcher/shutdown.h"
#include "overlay/overlay.h" #include "overlay/overlay.h"
@@ -401,7 +402,9 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
if (is_popn_sub_window) { if (is_popn_sub_window) {
POPN_SUBSCREEN_WINDOW = result; 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); disable_touch_indicators(result);
@@ -671,6 +674,13 @@ static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
return true; 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 // call original
return ShowWindow_orig(hWnd, nCmdShow); return ShowWindow_orig(hWnd, nCmdShow);
} }
+4
View File
@@ -590,6 +590,10 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::PopnMusicForceSDMode].value_bool()) { if (options[launcher::Options::PopnMusicForceSDMode].value_bool()) {
avs::ea3::PCB_TYPE = 0; 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()) { if (options[launcher::Options::LoadMetalGearArcadeModule].value_bool()) {
attach_mga = true; attach_mga = true;
} }
+9
View File
@@ -988,6 +988,15 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Pop'n Music", .game_name = "Pop'n Music",
.category = "Game Options", .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", .title = "Force Load HELLO! Pop'n Music Module",
.name = "hpm", .name = "hpm",
+1
View File
@@ -99,6 +99,7 @@ namespace launcher {
LoadPopnMusicModule, LoadPopnMusicModule,
PopnMusicForceHDMode, PopnMusicForceHDMode,
PopnMusicForceSDMode, PopnMusicForceSDMode,
PopnNoSub,
LoadHelloPopnMusicModule, LoadHelloPopnMusicModule,
LoadGitaDoraModule, LoadGitaDoraModule,
GitaDoraTwoChannelAudio, GitaDoraTwoChannelAudio,
+5
View File
@@ -12,6 +12,7 @@
#include "cfg/screen_resize.h" #include "cfg/screen_resize.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "overlay/overlay.h" #include "overlay/overlay.h"
#include "overlay/windows/generic_sub.h" #include "overlay/windows/generic_sub.h"
@@ -405,6 +406,10 @@ namespace wintouchemu {
// use mouse position as ImGui overlay will block the touch window // use mouse position as ImGui overlay will block the touch window
log_info("wintouchemu", "use mouse cursor API for ldj overlay subscreen"); log_info("wintouchemu", "use mouse cursor API for ldj overlay subscreen");
USE_MOUSE = true; 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) { } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen"); log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen");
USE_MOUSE = true; USE_MOUSE = true;
+4
View File
@@ -5,6 +5,7 @@
#include "games/io.h" #include "games/io.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "touch/touch.h" #include "touch/touch.h"
@@ -35,6 +36,7 @@
#include "windows/iopanel_ddr.h" #include "windows/iopanel_ddr.h"
#include "windows/iopanel_gfdm.h" #include "windows/iopanel_gfdm.h"
#include "windows/iopanel_iidx.h" #include "windows/iopanel_iidx.h"
#include "windows/popn_sub.h"
#include "windows/sdvx_sub.h" #include "windows/sdvx_sub.h"
#include "windows/keypad.h" #include "windows/keypad.h"
#include "windows/log.h" #include "windows/log.h"
@@ -415,6 +417,8 @@ void overlay::SpiceOverlay::init() {
window_sub = new overlay::windows::SDVXSubScreen(this); window_sub = new overlay::windows::SDVXSubScreen(this);
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) { } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
window_sub = new overlay::windows::GitaDoraSubScreen(this); window_sub = new overlay::windows::GitaDoraSubScreen(this);
} else if (games::popn::is_pikapika_model()) {
window_sub = new overlay::windows::PopnSubScreen(this);
} }
if (window_sub) { if (window_sub) {
@@ -4,6 +4,7 @@
#include "util/logging.h" #include "util/logging.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"
namespace overlay::windows { namespace overlay::windows {
@@ -72,6 +73,8 @@ namespace overlay::windows {
sub = "Show DRS Dance Floor"; sub = "Show DRS Dance Floor";
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) { } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
sub = "Show GITADORA Subscreen"; 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); build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false);
+53
View File
@@ -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 <spec>)!";
} 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;
}
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <windows.h>
#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();
}
};
}