diff --git a/src/spice2x/games/gitadora/gitadora.cpp b/src/spice2x/games/gitadora/gitadora.cpp index 92524cb..8fcd70f 100644 --- a/src/spice2x/games/gitadora/gitadora.cpp +++ b/src/spice2x/games/gitadora/gitadora.cpp @@ -13,6 +13,8 @@ #include "util/logging.h" #include "util/sigscan.h" #include "util/socd_cleaner.h" +#include "util/sysutils.h" +#include "util/utils.h" #include "hooks/setupapihook.h" namespace games::gitadora { @@ -20,7 +22,6 @@ namespace games::gitadora { // settings bool TWOCHANNEL = false; std::optional CAB_TYPE = std::nullopt; - bool ARENA_SINGLE_WINDOW = false; bool P1_LEFTY = false; bool P2_LEFTY = false; std::optional SUBSCREEN_OVERLAY_SIZE; @@ -223,8 +224,7 @@ namespace games::gitadora { // arena model launches a tiny window yet backbuffer at 4k, resulting in unusable overlay // force scaling to make things usable - if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() && - GRAPHICS_WINDOWED && !cfg::CONFIGURATOR_STANDALONE) { + if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() && !cfg::CONFIGURATOR_STANDALONE) { overlay::UI_SCALE_PERCENT = 250; } @@ -237,9 +237,292 @@ namespace games::gitadora { } else { log_info("gitadora", "guitar pick SOCD algorithm: legacy"); } + +#if SPICE64 && !SPICE_XP + + if (is_arena_model() && !GRAPHICS_WINDOWED && !GRAPHICS_FORCE_SINGLE_ADAPTER) { + const auto &monitors = sysutils::enumerate_monitors(); + const size_t active_count = monitors.size(); + log_info("gitadora", "arena model: detected {} active monitor(s)", active_count); + if (active_count < 4) { + log_info("gitadora", "arena model: enable single monitor mode due to insufficient monitors"); + GRAPHICS_FORCE_SINGLE_ADAPTER = true; + GRAPHICS_PREVENT_SECONDARY_WINDOW = true; + } + } + +#endif + } } +#if SPICE64 && !SPICE_XP + + static decltype(GetDisplayConfigBufferSizes) *GetDisplayConfigBufferSizes_orig = nullptr; + static decltype(QueryDisplayConfig) *QueryDisplayConfig_orig = nullptr; + static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr; + + // cached primary real monitor: its path + source/target mode entries. + // modeInfoIdx values on the path are renumbered to 0 / 1 so the cache is self-contained. + static DISPLAYCONFIG_PATH_INFO real_primary_path = {}; + static DISPLAYCONFIG_MODE_INFO real_primary_modes[2] = {}; // [0]=source, [1]=target + + // fake monitors appended after the real ones. the game classifies monitors + // by outputTechnology + connectorInstance: + // HDMI -> main 4k monitor (real primary) + // DP connInstance 0 -> left + // DP connInstance 1 -> right + // DP connInstance 2 -> small (sub/touch) + // ids are negated on the fake monitor headers so they can be distinguished + // from real ones in DisplayConfigGetDeviceInfo. + struct FakeMonitor { + LONG id; + int width; + int height; + int offset_x; + int offset_y; + UINT32 connector_instance; + }; + + // ORDERING MATTERS: the d3d9 wrapper (FAKE_SUBSCREEN_ADAPTER) hands out + // adapters as "\\.\DISPLAY_SPICE_FAKE_{N}" for N=1,2,3. The game maps each + // adapter to a swap chain role via its DisplayConfig connector instance, + // so the entries here must be listed in the same order the wrapper enumerates + // them: id=1 -> adapter 1 -> left, id=2 -> adapter 2 -> right, id=3 -> adapter 3 -> small. + static constexpr FakeMonitor FAKE_MONITORS[] = { + { 1, 1080, 1920, -100000, -100000, 0 }, // left (DP connector instance 0) + { 2, 1080, 1920, -200000, -200000, 1 }, // right (DP connector instance 1) + { 3, 800, 1280, -300000, -300000, 2 }, // small (DP connector instance 2, touch) + }; + static constexpr UINT32 FAKE_MONITOR_COUNT = static_cast(std::size(FAKE_MONITORS)); + + // call QueryDisplayConfig once, keep only the primary monitor's path and its + // two referenced modes (source + target). modeInfoIdx values are rewritten to + // 0 and 1 so the cache is self-consistent. + static void cache_primary_monitor_info() { + UINT32 path_count = 0; + UINT32 mode_count = 0; + if (GetDisplayConfigBufferSizes_orig( + QDC_ONLY_ACTIVE_PATHS, &path_count, &mode_count) != ERROR_SUCCESS) { + log_fatal("gitadora", "cache_primary_monitor_info: GetDisplayConfigBufferSizes failed"); + } + + std::vector all_paths(path_count); + std::vector all_modes(mode_count); + if (QueryDisplayConfig_orig( + QDC_ONLY_ACTIVE_PATHS, + &path_count, all_paths.data(), + &mode_count, all_modes.data(), + nullptr) != ERROR_SUCCESS) { + log_fatal("gitadora", "cache_primary_monitor_info: QueryDisplayConfig failed"); + } + all_paths.resize(path_count); + all_modes.resize(mode_count); + + // pick the primary monitor: source mode at (0, 0) + auto primary = std::find_if(all_paths.begin(), all_paths.end(), + [&](const auto &p) { + const auto idx = p.sourceInfo.modeInfoIdx; + return idx < all_modes.size() && + all_modes[idx].infoType == DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE && + all_modes[idx].sourceMode.position.x == 0 && + all_modes[idx].sourceMode.position.y == 0; + }); + if (primary == all_paths.end()) { + log_fatal("gitadora", "cache_primary_monitor_info: no primary monitor found"); + } + + real_primary_modes[0] = all_modes[primary->sourceInfo.modeInfoIdx]; + real_primary_modes[1] = all_modes[primary->targetInfo.modeInfoIdx]; + real_primary_path = *primary; + real_primary_path.sourceInfo.modeInfoIdx = 0; + real_primary_path.targetInfo.modeInfoIdx = 1; + + log_info("gitadora", "cache_primary_monitor_info: cached primary monitor"); + } + + static + LONG + WINAPI + GetDisplayConfigBufferSizes_hook( + UINT32 Flags, + UINT32 *pNumPathArrayElements, + UINT32 *pNumModeInfoArrayElements) + { + // populate cached primary real monitor on the first call + static std::once_flag populate_once; + std::call_once(populate_once, cache_primary_monitor_info); + + // always report exactly 1 real + N fake monitors + *pNumPathArrayElements = 1 + FAKE_MONITOR_COUNT; + *pNumModeInfoArrayElements = 2 + FAKE_MONITOR_COUNT * 2; + + log_info( + "gitadora", + "GetDisplayConfigBufferSizes: 1 real path + {} fake monitor(s)", + FAKE_MONITOR_COUNT); + + return ERROR_SUCCESS; + } + + // write fake monitor i into the caller's path/mode arrays. layout (single-monitor + // assumption): index 0 in both arrays holds the cached primary real monitor, so + // fake i occupies path slot (1 + i) and mode slots (2 + i*2) / (2 + i*2 + 1). + static void insert_fake_monitor( + DISPLAYCONFIG_PATH_INFO *paths, + DISPLAYCONFIG_MODE_INFO *modes, + UINT32 i) + { + const FakeMonitor &m = FAKE_MONITORS[i]; + const UINT32 src_idx = 2 + i * 2; + const UINT32 tgt_idx = src_idx + 1; + const LUID adapter_id { .LowPart = static_cast(-m.id), .HighPart = -m.id }; + const UINT32 uid = static_cast(-m.id); + + paths[1 + i] = { + .sourceInfo = { + .adapterId = adapter_id, + .id = uid, + .modeInfoIdx = src_idx, + .statusFlags = DISPLAYCONFIG_SOURCE_IN_USE, + }, + .targetInfo = { + .adapterId = adapter_id, + .id = uid, + .modeInfoIdx = tgt_idx, + .outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL, + .rotation = DISPLAYCONFIG_ROTATION_IDENTITY, + .scaling = DISPLAYCONFIG_SCALING_IDENTITY, + .refreshRate = { .Numerator = 60000, .Denominator = 1000 }, + .scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE, + .targetAvailable = TRUE, + .statusFlags = DISPLAYCONFIG_TARGET_IN_USE, + }, + .flags = DISPLAYCONFIG_PATH_ACTIVE, + }; + + modes[src_idx] = { + .infoType = DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE, + .id = uid, + .adapterId = adapter_id, + .sourceMode = { + .width = static_cast(m.width), + .height = static_cast(m.height), + .pixelFormat = DISPLAYCONFIG_PIXELFORMAT_32BPP, + .position = { .x = m.offset_x, .y = m.offset_y }, + }, + }; + + modes[tgt_idx] = { + .infoType = DISPLAYCONFIG_MODE_INFO_TYPE_TARGET, + .id = uid, + .adapterId = adapter_id, + .targetMode = {}, + }; + + log_misc( + "gitadora", + "inserted fake monitor: id={}, width={}, height={}, offset_x={}, offset_y={}", + m.id, m.width, m.height, m.offset_x, m.offset_y); + } + + static + LONG + WINAPI + QueryDisplayConfig_hook( + UINT32 flags, + UINT32* numPathArrayElements, + DISPLAYCONFIG_PATH_INFO* pathArray, + UINT32* numModeInfoArrayElements, + DISPLAYCONFIG_MODE_INFO* modeInfoArray, + DISPLAYCONFIG_TOPOLOGY_ID* currentTopologyId) + { + // copy cached primary real monitor into caller buffers at index 0 + pathArray[0] = real_primary_path; + modeInfoArray[0] = real_primary_modes[0]; + modeInfoArray[1] = real_primary_modes[1]; + *numPathArrayElements = 1 + FAKE_MONITOR_COUNT; + *numModeInfoArrayElements = 2 + FAKE_MONITOR_COUNT * 2; + if (currentTopologyId != nullptr) { + *currentTopologyId = DISPLAYCONFIG_TOPOLOGY_EXTEND; + } + + log_misc("gitadora", "QueryDisplayConfig returning fake monitor paths and modes"); + + // append fake monitors after the real one + for (UINT32 i = 0; i < FAKE_MONITOR_COUNT; i++) { + insert_fake_monitor(pathArray, modeInfoArray, i); + } + + return ERROR_SUCCESS; + } + + static + LONG + WINAPI + DisplayConfigGetDeviceInfo_hook(DISPLAYCONFIG_DEVICE_INFO_HEADER* requestPacket) + { + if (requestPacket == nullptr) { + return DisplayConfigGetDeviceInfo_orig(requestPacket); + } + + // handle fake monitors (negative id) directly without calling orig + const auto id = static_cast(requestPacket->id); + if (id < 0) { + if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) { + const auto sourceName = reinterpret_cast(requestPacket); + // name must match WrappedIDirect3D9::GetAdapterIdentifier + const std::string adapter_name = fmt::format("\\\\.\\DISPLAY_SPICE_FAKE_{}", -id); + wcscpy(sourceName->viewGdiDeviceName, s2ws(adapter_name).c_str()); + log_misc("gitadora", + "DisplayConfigGetDeviceInfo: fake source id={} name={}", id, adapter_name); + return ERROR_SUCCESS; + } + if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) { + const auto targetName = reinterpret_cast(requestPacket); + const LONG fake_id = -id; + UINT32 conn_inst = 0xff; + for (const auto& f : FAKE_MONITORS) { + if (f.id == fake_id) { + conn_inst = f.connector_instance; + break; + } + } + targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL; + targetName->connectorInstance = conn_inst; + wcscpy(targetName->monitorFriendlyDeviceName, L"Spice Fake Monitor"); + wcscpy(targetName->monitorDevicePath, L"\\\\?\\SpiceFakeMonitor"); + log_misc("gitadora", + "DisplayConfigGetDeviceInfo: fake target id={} -> DP connInst {}", + id, targetName->connectorInstance); + return ERROR_SUCCESS; + } + } + + const auto ret = DisplayConfigGetDeviceInfo_orig(requestPacket); + if (ret != ERROR_SUCCESS || + requestPacket->type != DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) { + return ret; + } + + // override the cached primary real monitor target info to look like HDMI/0 + const auto targetName = reinterpret_cast(requestPacket); + const auto& target = real_primary_path.targetInfo; + if (target.id == targetName->header.id && + target.adapterId.HighPart == targetName->header.adapterId.HighPart && + target.adapterId.LowPart == targetName->header.adapterId.LowPart) + { + targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI; + targetName->connectorInstance = 0; + log_info("gitadora", + "overriding primary monitor (id={}) to pretend to be HDMI", + targetName->header.id); + } + return ret; + } + +#endif + void GitaDoraGame::attach() { Game::attach(); @@ -296,12 +579,31 @@ namespace games::gitadora { // volume change prevention hooks::audio::mme::init(avs::game::DLL_INSTANCE); - - // touch hook - if (ARENA_SINGLE_WINDOW) { + + // monitor/touch hooks (windowed or full screen) + if (GRAPHICS_FORCE_SINGLE_ADAPTER || GRAPHICS_PREVENT_SECONDARY_WINDOW) { + // enable touch hook for subscreen overlay wintouchemu::FORCE = true; wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true; wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE); + +#if !SPICE_XP + + if (!GRAPHICS_WINDOWED) { + // monitor hook: always pretend to have 1 primary real monitor + 3 fake monitors + // (LEFT / RIGHT / SMALL) so the game accepts the arena cab topology + GetDisplayConfigBufferSizes_orig = + detour::iat_try("GetDisplayConfigBufferSizes", + GetDisplayConfigBufferSizes_hook, avs::game::DLL_INSTANCE); + QueryDisplayConfig_orig = + detour::iat_try("QueryDisplayConfig", + QueryDisplayConfig_hook, avs::game::DLL_INSTANCE); + DisplayConfigGetDeviceInfo_orig = + detour::iat_try("DisplayConfigGetDeviceInfo", + DisplayConfigGetDeviceInfo_hook, avs::game::DLL_INSTANCE); + } +#endif + } return; } diff --git a/src/spice2x/games/gitadora/gitadora.h b/src/spice2x/games/gitadora/gitadora.h index ffe0d57..f3662ec 100644 --- a/src/spice2x/games/gitadora/gitadora.h +++ b/src/spice2x/games/gitadora/gitadora.h @@ -11,7 +11,6 @@ namespace games::gitadora { // settings extern bool TWOCHANNEL; extern std::optional CAB_TYPE; - extern bool ARENA_SINGLE_WINDOW; extern bool P1_LEFTY; extern bool P2_LEFTY; extern std::optional SUBSCREEN_OVERLAY_SIZE; diff --git a/src/spice2x/games/popn/popn.cpp b/src/spice2x/games/popn/popn.cpp index c6b4fe6..5695872 100644 --- a/src/spice2x/games/popn/popn.cpp +++ b/src/spice2x/games/popn/popn.cpp @@ -261,7 +261,7 @@ namespace games::popn { } else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) { const auto source = reinterpret_cast(requestPacket); // value must match WrappedIDirect3D9::GetAdapterIdentifier - wcscpy(source->viewGdiDeviceName, L"\\\\.\\DISPLAY_SPICE_FAKE"); + wcscpy(source->viewGdiDeviceName, L"\\\\.\\DISPLAY_SPICE_FAKE_1"); } else { log_fatal( "popn", diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp index 084cef9..7c288f9 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_backend.cpp @@ -465,15 +465,21 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::RegisterSoftwareDevice(void *pIniti UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() { UINT result = pReal->GetAdapterCount(); - if (!FAKE_SUBSCREEN_ADAPTER && games::popn::is_pikapika_model() && result == 1) { - FAKE_SUBSCREEN_ADAPTER = true; - log_info( - "graphics::d3d9", - "GetAdapterCount returned 1, popn needs 2 adapters - enabliing fake submonitor mode"); - } + if (!FAKE_SUBSCREEN_ADAPTER) { + if (games::popn::is_pikapika_model() && result == 1) { + FAKE_SUBSCREEN_ADAPTER = true; + log_info( + "graphics::d3d9", + "GetAdapterCount returned {}, popn needs 2 adapters - enabling fake submonitor mode", result); + return 2; - if (FAKE_SUBSCREEN_ADAPTER) { - return 2; + } else if (games::gitadora::is_arena_model() && !GRAPHICS_WINDOWED && result < 4) { + FAKE_SUBSCREEN_ADAPTER = true; + log_info( + "graphics::d3d9", + "GetAdapterCount returned {}, gfdm needs 4 adapters - enabling fake submonitor mode", result); + return 4; + } } return result; @@ -485,12 +491,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier( D3DADAPTER_IDENTIFIER9 *pIdentifier) { - if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1 && pIdentifier) { + if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0 && pIdentifier) { *pIdentifier = {}; - strcpy(pIdentifier->DeviceName, "\\\\.\\DISPLAY_SPICE_FAKE"); + const std::string adapter_name = fmt::format("\\\\.\\DISPLAY_SPICE_FAKE_{}", Adapter); + strcpy(pIdentifier->DeviceName, adapter_name.c_str()); log_misc( "graphics::d3d9", - "GetAdapterIdentifier called for fake subscreen adapter 1: {}", + "GetAdapterIdentifier called for fake subscreen adapter {}: {}", + Adapter, pIdentifier->DeviceName); return S_OK; } @@ -499,8 +507,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier( } UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) { - if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) { - log_misc("graphics::d3d9", "GetAdapterModeCount called for fake subscreen adapter"); + if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0) { + log_misc("graphics::d3d9", "GetAdapterModeCount called for fake subscreen adapter {}", Adapter); return 1; } @@ -513,8 +521,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes( UINT Mode, D3DDISPLAYMODE *pMode) { - if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) { - log_misc("graphics::d3d9", "EnumAdapterModes called for fake subscreen adapter"); + if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0) { + log_misc("graphics::d3d9", "EnumAdapterModes called for fake subscreen adapter {}", Adapter); if (Mode == 0 && pMode) { pMode->Width = 1280; pMode->Height = 800; @@ -622,8 +630,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormat( D3DRESOURCETYPE RType, D3DFORMAT CheckFormat) { - if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) { - log_misc("graphics::d3d9", "CheckDeviceFormat called for fake subscreen adapter"); + if (FAKE_SUBSCREEN_ADAPTER && Adapter > 0) { + log_misc("graphics::d3d9", "CheckDeviceFormat called for fake subscreen adapter {}", Adapter); return D3D_OK; } @@ -1143,10 +1151,15 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx( *ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface); - // initialize sub screen if IIDX/SDVX requested a multi-head context - if (avs::game::is_model({"LDJ", "KFC", "M39"}) && + // initialize sub screen if the game requested a multi-head context + if (avs::game::is_model({"LDJ", "KFC", "M39", "M32"}) && (orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) { - graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[1]); + + UINT i = 1; + if (games::gitadora::is_arena_model()) { + i = 2; + } + graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[i]); } } @@ -1196,7 +1209,13 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE log_info("graphics::d3d9", "created additional swap chain for windowed mode"); } } else { - hr = device->GetSwapChain(1, &SUB_SWAP_CHAIN); + + int swapchain = 1; + if (games::gitadora::is_arena_model()) { + swapchain = 2; + } + + hr = device->GetSwapChain(swapchain, &SUB_SWAP_CHAIN); if (FAILED(hr)) { log_warning("graphics::d3d9", "failed to acquire fullscreen sub swap chain, hr={}", FMT_HRESULT(hr)); } else { @@ -1233,7 +1252,7 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) { // iidx/sdvx int swapchain = 1; if (games::gitadora::is_arena_model()) { - swapchain = 3; + swapchain = 2; } if (!ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE && SUB_SWAP_CHAIN == nullptr) { @@ -1452,8 +1471,9 @@ void graphics_d3d9_on_present( // for IIDX TDJ / SDVX UFC, handle subscreen const bool is_vm = games::sdvx::is_valkyrie_model(); const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE; - const bool is_gfdm_arena = - games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW; + const bool is_gfdm_arena = games::gitadora::is_arena_model() && + (GRAPHICS_FORCE_SINGLE_ADAPTER || GRAPHICS_PREVENT_SECONDARY_WINDOW); + const bool is_pika = games::popn::is_pikapika_model(); if (is_vm || is_tdj || is_gfdm_arena || is_pika) { graphics_d3d9_ldj_on_present(wrapped_device); diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp index 40e6800..1d9738f 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.cpp @@ -129,13 +129,17 @@ ULONG STDMETHODCALLTYPE WrappedIDirect3DDevice9::Release() { this->main_swapchain->Release(); this->main_swapchain = nullptr; } - if (this->sub_swapchain) { - this->sub_swapchain->Release(); - this->sub_swapchain = nullptr; + for (auto &sc : this->sub_swapchain) { + if (sc) { + sc->Release(); + sc = nullptr; + } } - if (this->fake_sub_swapchain) { - this->fake_sub_swapchain->Release(); - this->fake_sub_swapchain = nullptr; + for (auto &sc : this->fake_sub_swapchain) { + if (sc) { + sc->Release(); + sc = nullptr; + } } if (overlay::ENABLED) { @@ -250,27 +254,47 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain( WRAP_VERBOSE; HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain); + + int index = 0; bool create_swap_chain = false; if (avs::game::is_model({"LDJ", "KFC", "M39"})) { create_swap_chain = true; + } else if (games::gitadora::is_arena_model() && - games::gitadora::ARENA_SINGLE_WINDOW && - pPresentationParameters->BackBufferWidth == 800) { - create_swap_chain = true; + (GRAPHICS_FORCE_SINGLE_ADAPTER || GRAPHICS_PREVENT_SECONDARY_WINDOW)) { + if (pPresentationParameters->BackBufferWidth == 800) { + // SMALL (subscreen) + create_swap_chain = true; + index = 0; + } else if (pPresentationParameters->BackBufferWidth == 1080) { + // LEFT/RIGHT + create_swap_chain = true; + index = 1; + if (sub_swapchain[index] || fake_sub_swapchain[index]) { + index = 2; + } + } else { + log_warning("graphics::d3d9", "unknown swap chain detected in CreateAdditionalSwapChain"); + } } if (create_swap_chain) { - if (SUCCEEDED(hr) && !sub_swapchain) { - sub_swapchain = new WrappedIDirect3DSwapChain9(this, *ppSwapChain); - sub_swapchain->should_run_hooks = false; - } else if (FAILED(hr) && !fake_sub_swapchain) { + log_misc( + "graphics::d3d9", + "CreateAdditionalSwapChain called for swap chain {}, creating swap chain", + index); + + if (SUCCEEDED(hr) && !sub_swapchain[index]) { + sub_swapchain[index] = new WrappedIDirect3DSwapChain9(this, *ppSwapChain); + sub_swapchain[index]->should_run_hooks = false; + } else if (FAILED(hr) && !fake_sub_swapchain[index]) { log_warning("graphics::d3d9", "failed to create sub swap chain, hr={}, using fake swap chain", FMT_HRESULT(hr)); - fake_sub_swapchain = new FakeIDirect3DSwapChain9(this, pPresentationParameters, false); - fake_sub_swapchain->AddRef(); - *ppSwapChain = static_cast(fake_sub_swapchain); + fake_sub_swapchain[index] = new FakeIDirect3DSwapChain9(this, pPresentationParameters, false); + fake_sub_swapchain[index]->AddRef(); + *ppSwapChain = static_cast(fake_sub_swapchain[index]); return D3D_OK; } @@ -308,22 +332,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain( if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC", "M39"})) { swap = true; } - if (games::gitadora::is_arena_model() && - games::gitadora::ARENA_SINGLE_WINDOW && - iSwapChain == 3) { + if (games::gitadora::is_arena_model() && iSwapChain == 2) { swap = true; } if (swap) { - if (sub_swapchain) { - sub_swapchain->AddRef(); - *ppSwapChain = static_cast(sub_swapchain); + if (sub_swapchain[0]) { + sub_swapchain[0]->AddRef(); + *ppSwapChain = static_cast(sub_swapchain[0]); graphics_screens_register(iSwapChain); return D3D_OK; - } else if (fake_sub_swapchain) { - fake_sub_swapchain->AddRef(); - *ppSwapChain = static_cast(fake_sub_swapchain); + } else if (fake_sub_swapchain[0]) { + fake_sub_swapchain[0]->AddRef(); + *ppSwapChain = static_cast(fake_sub_swapchain[0]); graphics_screens_register(iSwapChain); return D3D_OK; @@ -344,7 +366,7 @@ UINT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetNumberOfSwapChains() { UINT n = pReal->GetNumberOfSwapChains(); - if (sub_swapchain && avs::game::is_model({"LDJ", "KFC", "M39"})) { + if (sub_swapchain[0] && avs::game::is_model({"LDJ", "KFC", "M39"})) { n += 1; } diff --git a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.h b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.h index e511e6d..be82009 100644 --- a/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.h +++ b/src/spice2x/hooks/graphics/backends/d3d9/d3d9_device.h @@ -204,7 +204,8 @@ struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex { std::atomic_ulong refs = 1; WrappedIDirect3DSwapChain9 *main_swapchain = nullptr; - WrappedIDirect3DSwapChain9 *sub_swapchain = nullptr; - FakeIDirect3DSwapChain9 *fake_sub_swapchain = nullptr; + WrappedIDirect3DSwapChain9 *sub_swapchain[3] = { nullptr, nullptr, nullptr }; + FakeIDirect3DSwapChain9 *fake_sub_swapchain[3] = { nullptr, nullptr, nullptr }; + IDirect3DVertexShader9 *vertex_shader = nullptr; }; diff --git a/src/spice2x/hooks/graphics/graphics.cpp b/src/spice2x/hooks/graphics/graphics.cpp index 13f35a3..da25415 100644 --- a/src/spice2x/hooks/graphics/graphics.cpp +++ b/src/spice2x/hooks/graphics/graphics.cpp @@ -421,7 +421,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC } // only hook touch window if multiple windows are allowed - if (is_gfdm_sub_window && !games::gitadora::ARENA_SINGLE_WINDOW) { + if (is_gfdm_sub_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOW) { GFDM_SUBSCREEN_WINDOW = result; graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW); } @@ -702,7 +702,7 @@ static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter, static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) { if (games::gitadora::is_arena_model() && - games::gitadora::ARENA_SINGLE_WINDOW && + GRAPHICS_PREVENT_SECONDARY_WINDOW && hWnd != GRAPHICS_HOOKED_WINDOW) { log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd)); return true; diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index 67b67a3..e1b243c 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -644,8 +644,11 @@ int main_implementation(int argc, char *argv[]) { if (options[launcher::Options::GitaDoraCabinetType].is_active()) { games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32(); } - if (options[launcher::Options::GitaDoraArenaSingleWindow].value_bool() && GRAPHICS_WINDOWED) { - games::gitadora::ARENA_SINGLE_WINDOW = true; + if (options[launcher::Options::GitaDoraArenaSingleWindow].value_bool()) { + // for full screen + GRAPHICS_FORCE_SINGLE_ADAPTER = true; + // for windowed + GRAPHICS_PREVENT_SECONDARY_WINDOW = true; } if (options[launcher::Options::GitaDoraWailHold].is_active()) { socd::TILT_HOLD_MS = options[launcher::Options::GitaDoraWailHold].value_uint32(); diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index 9ed847b..e4672b3 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -1056,9 +1056,12 @@ static const std::vector OPTION_DEFINITIONS = { }, { // GitaDoraArenaSingleWindow - .title = "GitaDora Arena Single Window (EXPERIMENTAL)", + .title = "GitaDora Arena Disable Subscreens (EXPERIMENTAL)", .name = "gdaonewindow", - .desc = "For Arena Model, when combined with windowed mode, only show the main game window; enables subscreen overlay.", + .desc = "For Arena Model:\n\n" + "Windowed mode: instead of 4 windows, create 1 window.\n\n" + "Fullscreen mode: instead of requiring 4 monitors, use only the primary monitor. WARNING: requires 4K monitor.\n\n" + "To access the subscreen, use the subscreen overlay.", .type = OptionType::Bool, .game_name = "GitaDora", .category = "Game Options", diff --git a/src/spice2x/misc/wintouchemu.cpp b/src/spice2x/misc/wintouchemu.cpp index d916d56..0432925 100644 --- a/src/spice2x/misc/wintouchemu.cpp +++ b/src/spice2x/misc/wintouchemu.cpp @@ -416,7 +416,7 @@ namespace wintouchemu { // 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() && GRAPHICS_PREVENT_SECONDARY_WINDOW) { log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen"); USE_MOUSE = true; } else { diff --git a/src/spice2x/overlay/overlay.cpp b/src/spice2x/overlay/overlay.cpp index 7fa0c14..157141a 100644 --- a/src/spice2x/overlay/overlay.cpp +++ b/src/spice2x/overlay/overlay.cpp @@ -415,7 +415,7 @@ void overlay::SpiceOverlay::init() { window_sub = new overlay::windows::DRSDanceFloorDisplay(this); } else if (avs::game::is_model("KFC")) { 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()) { window_sub = new overlay::windows::GitaDoraSubScreen(this); } else if (games::popn::is_pikapika_model()) { window_sub = new overlay::windows::PopnSubScreen(this); diff --git a/src/spice2x/overlay/windows/exitprompt.cpp b/src/spice2x/overlay/windows/exitprompt.cpp index 8b36e72..4e286dc 100644 --- a/src/spice2x/overlay/windows/exitprompt.cpp +++ b/src/spice2x/overlay/windows/exitprompt.cpp @@ -71,7 +71,7 @@ namespace overlay::windows { sub = "Show Valkyrie Subscreen"; } else if (avs::game::is_model("REC")) { sub = "Show DRS Dance Floor"; - } else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) { + } else if (games::gitadora::is_arena_model()) { sub = "Show GITADORA Subscreen"; } else if (games::popn::is_pikapika_model()) { sub = "Show Pop'n Subscreen"; diff --git a/src/spice2x/overlay/windows/gfdm_sub.cpp b/src/spice2x/overlay/windows/gfdm_sub.cpp index 5ce5ad6..201293d 100644 --- a/src/spice2x/overlay/windows/gfdm_sub.cpp +++ b/src/spice2x/overlay/windows/gfdm_sub.cpp @@ -11,6 +11,12 @@ namespace overlay::windows { if (!games::gitadora::is_arena_model()) { this->disabled_message = "Requires Arena Model!"; + } else if (!GRAPHICS_PREVENT_SECONDARY_WINDOW) { + if (GRAPHICS_WINDOWED) { + this->disabled_message = "Use the dedicated subscreen window!"; + } else { + this->disabled_message = "Subscreen overlay disabled by user; enable single window mode for overlay."; + } } this->resize_callback = keep_10_by_16;