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 "cfg/button.h"
#include "cfg/api.h"
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h"
#include "launcher/launcher.h"
#include "launcher/logger.h"
#include "misc/eamuse.h"
#include "util/sysutils.h"
#include "io.h"
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() {
return 1;
}
@@ -320,5 +391,29 @@ namespace games::popn {
detour::inline_hook((void *) usbWdtStartDone,
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
}
}