popn: hook DisplayConfigGetDeviceInfo to allow for any GPU port to be used for monitor, get past I/O check (#622)

## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
The game expects the main display to be *not* HDMI, and at port 2.

Hooking DisplayConfigGetDeviceInfo allows us to boot the game on any
monitor.

Add basic I/O hook. It gets stuck in `USB I/O Checking...` for a couple
minutes but it'll eventually skip and boot to title screen.

## Testing
ugh.
This commit is contained in:
bicarus
2026-04-10 02:56:32 -07:00
committed by GitHub
parent 54ec9f272e
commit b281517429
6 changed files with 129 additions and 7 deletions
+95
View File
@@ -9,14 +9,85 @@
#include "util/utils.h" #include "util/utils.h"
#include "cfg/button.h" #include "cfg/button.h"
#include "cfg/api.h" #include "cfg/api.h"
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h" #include "hooks/sleephook.h"
#include "launcher/launcher.h" #include "launcher/launcher.h"
#include "launcher/logger.h" #include "launcher/logger.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/sysutils.h"
#include "io.h" #include "io.h"
namespace games::popn { namespace games::popn {
#if SPICE64 && !SPICE_XP
static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr;
static
LONG
WINAPI
DisplayConfigGetDeviceInfo_hook(DISPLAYCONFIG_DEVICE_INFO_HEADER* requestPacket)
{
if (requestPacket == nullptr) {
return DisplayConfigGetDeviceInfo_orig(requestPacket);
}
const auto ret = DisplayConfigGetDeviceInfo_orig(requestPacket);
log_misc(
"popn",
"DisplayConfigGetDeviceInfo returned {}, type={}, size={}, id={}, luid={}/{}",
ret,
static_cast<int>(requestPacket->type),
requestPacket->size,
requestPacket->id,
requestPacket->adapterId.HighPart,
requestPacket->adapterId.LowPart);
if (ret == ERROR_SUCCESS) {
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
const auto sourceName = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
log_misc(
"popn",
"... name={}",
ws2s(sourceName->viewGdiDeviceName));
} else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
const auto targetName = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
log_misc(
"popn",
"... flags={}, outputTechnology: {}, connectorInstance={}, friendlyname={} path={}",
targetName->flags.value,
static_cast<int>(targetName->outputTechnology),
targetName->connectorInstance,
ws2s(targetName->monitorFriendlyDeviceName),
ws2s(targetName->monitorDevicePath));
// need to fix up some values for the primary monitor...
const auto monitors = sysutils::enumerate_monitors();
for (const auto& monitor : monitors) {
if (monitor.id == targetName->header.id &&
monitor.adapter_id_HighPart == targetName->header.adapterId.HighPart &&
monitor.adapter_id_LowPart == targetName->header.adapterId.LowPart) {
if (monitor.is_primary) {
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL;
targetName->connectorInstance = 2;
log_info(
"popn",
"... overriding primary monitor ({}) to pretend to be DP port #2",
monitor.display_name);
} else {
// TODO: is this what the game expects for subscreen?
// TODO: what if there are 3+ monitors?
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
}
break;
}
}
}
}
return ret;
}
#endif
static int __cdecl usbCheckAlive() { static int __cdecl usbCheckAlive() {
return 1; return 1;
} }
@@ -320,5 +391,29 @@ namespace games::popn {
detour::inline_hook((void *) usbWdtStartDone, detour::inline_hook((void *) usbWdtStartDone,
libutils::try_proc(ezusb, "?usbWdtStartDone@@YAHXZ")); libutils::try_proc(ezusb, "?usbWdtStartDone@@YAHXZ"));
} }
#if SPICE64 && !SPICE_XP
if (is_pikapika_model()) {
// monitor hook
DisplayConfigGetDeviceInfo_orig = detour::iat_try("DisplayConfigGetDeviceInfo", DisplayConfigGetDeviceInfo_hook, avs::game::DLL_INSTANCE);
// TODO: io emulation
SETUPAPI_SETTINGS settings{};
settings.class_guid[0] = 0x86E0D1E0;
settings.class_guid[1] = 0x11D08089;
settings.class_guid[2] = 0x0008E49C;
settings.class_guid[3] = 0x731F303E;
const char property[] = "1CCF(8050)_000";
const char property_hardwareid[] = "USB\\VID_1CCF&PID_8050&MI_00\\000";
memcpy(settings.property_devicedesc, property, sizeof(property));
memcpy(settings.property_hardwareid, property_hardwareid, sizeof(property_hardwareid));
setupapihook_init(avs::game::DLL_INSTANCE);
setupapihook_add(settings);
}
#endif
} }
} }
@@ -1139,8 +1139,6 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
*/ */
if (GRAPHICS_WINDOWED) { if (GRAPHICS_WINDOWED) {
log_info("graphics::d3d9", "creating additional swap chain");
present_params->Windowed = true; present_params->Windowed = true;
present_params->FullScreen_RefreshRateInHz = 0; present_params->FullScreen_RefreshRateInHz = 0;
@@ -1149,18 +1147,23 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN); hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to create additional swap chain, hr={}", FMT_HRESULT(hr)); log_warning("graphics::d3d9", "failed to create additional swap chain, hr={}", FMT_HRESULT(hr));
} else {
log_info("graphics::d3d9", "created additional swap chain for windowed mode");
} }
} else { } else {
hr = device->GetSwapChain(1, &SUB_SWAP_CHAIN); hr = device->GetSwapChain(1, &SUB_SWAP_CHAIN);
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to acquire fullscreen sub swap chain, hr={}", FMT_HRESULT(hr)); log_warning("graphics::d3d9", "failed to acquire fullscreen sub swap chain, hr={}", FMT_HRESULT(hr));
} else { } else {
log_info("graphics::d3d9", "acquired fullscreen sub swap chain");
return; return;
} }
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN); hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
if (FAILED(hr)) { if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to get additional swap chain, hr={}", FMT_HRESULT(hr)); log_warning("graphics::d3d9", "failed to get additional swap chain, hr={}", FMT_HRESULT(hr));
} else {
log_info("graphics::d3d9", "created additional swap chain for fullscreen mode");
} }
} }
} }
@@ -263,7 +263,6 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
if (SUCCEEDED(hr) && !sub_swapchain) { if (SUCCEEDED(hr) && !sub_swapchain) {
sub_swapchain = new WrappedIDirect3DSwapChain9(this, *ppSwapChain); sub_swapchain = new WrappedIDirect3DSwapChain9(this, *ppSwapChain);
sub_swapchain->should_run_hooks = false; sub_swapchain->should_run_hooks = false;
log_info("graphics::d3d9", "created additional swap chain");
} else if (FAILED(hr) && !fake_sub_swapchain) { } else if (FAILED(hr) && !fake_sub_swapchain) {
log_warning("graphics::d3d9", log_warning("graphics::d3d9",
"failed to create sub swap chain, hr={}, using fake swap chain", "failed to create sub swap chain, hr={}, using fake swap chain",
+5 -3
View File
@@ -4540,9 +4540,11 @@ namespace overlay::windows {
for (const auto &monitor : monitors) { for (const auto &monitor : monitors) {
const bool is_selected = option.value == monitor.display_name; const bool is_selected = option.value == monitor.display_name;
auto friendly = wchar_to_u8(monitor.friendly_name.c_str()); auto friendly = wchar_to_u8(monitor.friendly_name.c_str());
if (ImGui::Selectable( auto str = fmt::format("{} ({})", monitor.display_name, friendly);
fmt::format("{} ({})", monitor.display_name, friendly).c_str(), if (monitor.is_primary) {
is_selected)) { str += " [Primary]";
}
if (ImGui::Selectable(str.c_str(), is_selected)) {
option.value = monitor.display_name; option.value = monitor.display_name;
} }
} }
+20 -1
View File
@@ -409,7 +409,26 @@ namespace sysutils {
std::vector<MonitorEntry> result; std::vector<MonitorEntry> result;
for (const auto& path : paths) { for (const auto& path : paths) {
MonitorEntry entry; MonitorEntry entry = {};
entry.adapter_id_LowPart = path.targetInfo.adapterId.LowPart;
entry.adapter_id_HighPart = path.targetInfo.adapterId.HighPart;
entry.id = path.targetInfo.id;
// primary monitor?
for (const auto& mode : modes) {
if (mode.infoType != DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) {
continue;
}
if (mode.adapterId.HighPart == path.sourceInfo.adapterId.HighPart &&
mode.adapterId.LowPart == path.sourceInfo.adapterId.LowPart &&
mode.id == path.sourceInfo.id) {
if (mode.sourceMode.position.x == 0 && mode.sourceMode.position.y == 0) {
entry.is_primary = true;
}
break;
}
}
// device ID (\\.\DISPLAYn) // device ID (\\.\DISPLAYn)
DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name = {}; DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name = {};
+4
View File
@@ -12,6 +12,10 @@ namespace sysutils {
struct MonitorEntry { struct MonitorEntry {
std::string display_name; // \\.\DISPLAY1 std::string display_name; // \\.\DISPLAY1
std::wstring friendly_name; // Dell S2204T; wstring so that it can be converted to utf8 or string std::wstring friendly_name; // Dell S2204T; wstring so that it can be converted to utf8 or string
bool is_primary;
uint32_t adapter_id_LowPart;
int32_t adapter_id_HighPart;
uint32_t id;
}; };
const std::vector<MonitorEntry> &enumerate_monitors(); const std::vector<MonitorEntry> &enumerate_monitors();