mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
popn: allow game to launch on PCs with only one monitor (#633)
## Link to GitHub Issue or related Pull Request, if one exists #618 ## Description of change The game insists on having two monitors. Hook various DX9 and Win32 monitor APIs to fake a monitor if the user only has one. This correctly convinces the game that it needs to draw two screens instead of just one. ## Testing WIP
This commit is contained in:
@@ -27,8 +27,120 @@ namespace games::popn {
|
|||||||
|
|
||||||
#if SPICE64 && !SPICE_XP
|
#if SPICE64 && !SPICE_XP
|
||||||
|
|
||||||
|
const HMONITOR FAKE_MONITOR_HMONITOR = (HMONITOR)-1;
|
||||||
|
constexpr LONG FAKE_MONITOR_OFFSET_X_Y = -999999;
|
||||||
|
constexpr LONG FAKE_MONITOR_WIDTH = 1280;
|
||||||
|
constexpr LONG FAKE_MONITOR_HEIGHT = 800;
|
||||||
|
|
||||||
|
static decltype(GetDisplayConfigBufferSizes) *GetDisplayConfigBufferSizes_orig = nullptr;
|
||||||
|
static decltype(QueryDisplayConfig) *QueryDisplayConfig_orig = nullptr;
|
||||||
static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr;
|
static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr;
|
||||||
|
|
||||||
|
static UINT32 pNumPathArrayElements_original = 0;
|
||||||
|
static UINT32 pNumModeInfoArrayElements_original = 0;
|
||||||
|
|
||||||
|
static
|
||||||
|
LONG
|
||||||
|
WINAPI
|
||||||
|
GetDisplayConfigBufferSizes_hook(
|
||||||
|
UINT32 Flags,
|
||||||
|
UINT32 *pNumPathArrayElements,
|
||||||
|
UINT32 *pNumModeInfoArrayElements)
|
||||||
|
{
|
||||||
|
const auto ret = GetDisplayConfigBufferSizes_orig(Flags, pNumPathArrayElements, pNumModeInfoArrayElements);
|
||||||
|
if (FAKE_SUBSCREEN_ADAPTER) {
|
||||||
|
log_misc("popn", "GetDisplayConfigBufferSizes returning fake monitor paths and modes");
|
||||||
|
pNumPathArrayElements_original = *pNumPathArrayElements;
|
||||||
|
pNumModeInfoArrayElements_original = *pNumModeInfoArrayElements;
|
||||||
|
*pNumPathArrayElements += 1;
|
||||||
|
*pNumModeInfoArrayElements += 2;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
LONG
|
||||||
|
WINAPI
|
||||||
|
QueryDisplayConfig_hook(
|
||||||
|
UINT32 flags,
|
||||||
|
UINT32* numPathArrayElements,
|
||||||
|
DISPLAYCONFIG_PATH_INFO* pathArray,
|
||||||
|
UINT32* numModeInfoArrayElements,
|
||||||
|
DISPLAYCONFIG_MODE_INFO* modeInfoArray,
|
||||||
|
DISPLAYCONFIG_TOPOLOGY_ID* currentTopologyId)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!FAKE_SUBSCREEN_ADAPTER) {
|
||||||
|
return QueryDisplayConfig_orig(
|
||||||
|
flags,
|
||||||
|
numPathArrayElements, pathArray,
|
||||||
|
numModeInfoArrayElements, modeInfoArray,
|
||||||
|
currentTopologyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// call original to fill in real monitor info
|
||||||
|
UINT32 num_paths = pNumPathArrayElements_original;
|
||||||
|
UINT32 num_modes = pNumModeInfoArrayElements_original;
|
||||||
|
const auto ret = QueryDisplayConfig_orig(
|
||||||
|
flags,
|
||||||
|
&num_paths, pathArray,
|
||||||
|
&num_modes, modeInfoArray,
|
||||||
|
currentTopologyId);
|
||||||
|
|
||||||
|
if (ret != ERROR_SUCCESS) {
|
||||||
|
log_warning("popn", "QueryDisplayConfig failed with error code {}", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_misc("popn", "QueryDisplayConfig returning fake monitor paths and modes");
|
||||||
|
|
||||||
|
// insert a fake path
|
||||||
|
DISPLAYCONFIG_PATH_INFO *path = &pathArray[pNumPathArrayElements_original];
|
||||||
|
*path = {};
|
||||||
|
path->flags = DISPLAYCONFIG_PATH_ACTIVE;
|
||||||
|
|
||||||
|
path->sourceInfo.adapterId.HighPart = -1;
|
||||||
|
path->sourceInfo.adapterId.LowPart = -1;
|
||||||
|
path->sourceInfo.id = -1;
|
||||||
|
path->sourceInfo.modeInfoIdx = pNumModeInfoArrayElements_original;
|
||||||
|
path->sourceInfo.statusFlags = DISPLAYCONFIG_SOURCE_IN_USE;
|
||||||
|
|
||||||
|
path->targetInfo.adapterId.HighPart = -1;
|
||||||
|
path->targetInfo.adapterId.LowPart = -1;
|
||||||
|
path->targetInfo.id = -2;
|
||||||
|
path->targetInfo.modeInfoIdx = pNumModeInfoArrayElements_original + 1;
|
||||||
|
path->targetInfo.outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
|
||||||
|
path->targetInfo.rotation = DISPLAYCONFIG_ROTATION_IDENTITY;
|
||||||
|
path->targetInfo.scaling = DISPLAYCONFIG_SCALING_IDENTITY;
|
||||||
|
path->targetInfo.refreshRate.Numerator = 60000;
|
||||||
|
path->targetInfo.refreshRate.Denominator = 1000;
|
||||||
|
path->targetInfo.scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE;
|
||||||
|
path->targetInfo.targetAvailable = true;
|
||||||
|
path->targetInfo.statusFlags = DISPLAYCONFIG_TARGET_IN_USE;
|
||||||
|
|
||||||
|
// insert fake mode source
|
||||||
|
DISPLAYCONFIG_MODE_INFO *mode_source = &modeInfoArray[pNumModeInfoArrayElements_original];
|
||||||
|
*mode_source = {};
|
||||||
|
mode_source->infoType = DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE;
|
||||||
|
mode_source->id = -1;
|
||||||
|
mode_source->adapterId.HighPart = -1;
|
||||||
|
mode_source->adapterId.LowPart = -1;
|
||||||
|
mode_source->sourceMode.width = FAKE_MONITOR_WIDTH;
|
||||||
|
mode_source->sourceMode.height = FAKE_MONITOR_HEIGHT;
|
||||||
|
mode_source->sourceMode.pixelFormat = DISPLAYCONFIG_PIXELFORMAT_32BPP;
|
||||||
|
mode_source->sourceMode.position.x = FAKE_MONITOR_OFFSET_X_Y;
|
||||||
|
mode_source->sourceMode.position.y = FAKE_MONITOR_OFFSET_X_Y;
|
||||||
|
|
||||||
|
// insert fake mode target
|
||||||
|
DISPLAYCONFIG_MODE_INFO *mode_target = &modeInfoArray[pNumModeInfoArrayElements_original+1];
|
||||||
|
*mode_target = {};
|
||||||
|
mode_target->infoType = DISPLAYCONFIG_MODE_INFO_TYPE_TARGET;
|
||||||
|
mode_target->id = -2;
|
||||||
|
mode_target->adapterId.HighPart = -1;
|
||||||
|
mode_target->adapterId.LowPart = -1;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
LONG
|
LONG
|
||||||
WINAPI
|
WINAPI
|
||||||
@@ -37,6 +149,43 @@ namespace games::popn {
|
|||||||
if (requestPacket == nullptr) {
|
if (requestPacket == nullptr) {
|
||||||
return DisplayConfigGetDeviceInfo_orig(requestPacket);
|
return DisplayConfigGetDeviceInfo_orig(requestPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fake monitor
|
||||||
|
if (FAKE_SUBSCREEN_ADAPTER &&
|
||||||
|
(requestPacket->id == static_cast<UINT32>(-1) ||
|
||||||
|
requestPacket->id == static_cast<UINT32>(-2)) &&
|
||||||
|
requestPacket->adapterId.HighPart == static_cast<LONG>(-1) &&
|
||||||
|
requestPacket->adapterId.LowPart == static_cast<DWORD>(-1)) {
|
||||||
|
log_misc(
|
||||||
|
"popn",
|
||||||
|
"DisplayConfigGetDeviceInfo hook hit for fake monitor, type={}, size={}, id={}, luid={}/{}",
|
||||||
|
static_cast<int>(requestPacket->type),
|
||||||
|
requestPacket->size,
|
||||||
|
requestPacket->id,
|
||||||
|
requestPacket->adapterId.HighPart,
|
||||||
|
requestPacket->adapterId.LowPart);
|
||||||
|
|
||||||
|
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
|
||||||
|
const auto target = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
|
||||||
|
target->flags.value = 0;
|
||||||
|
target->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
|
||||||
|
target->connectorInstance = 0;
|
||||||
|
wcscpy(target->monitorFriendlyDeviceName, L"Spice Fake Monitor");
|
||||||
|
wcscpy(target->monitorDevicePath, L"\\\\?\\SpiceFakeMonitor");
|
||||||
|
} else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
|
||||||
|
const auto source = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
|
||||||
|
// value must match WrappedIDirect3D9::GetAdapterIdentifier
|
||||||
|
wcscpy(source->viewGdiDeviceName, L"\\\\.\\DISPLAY_SPICE_FAKE");
|
||||||
|
} else {
|
||||||
|
log_fatal(
|
||||||
|
"popn",
|
||||||
|
"unexpected device info type {} for fake monitor",
|
||||||
|
static_cast<int>(requestPacket->type));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
const auto ret = DisplayConfigGetDeviceInfo_orig(requestPacket);
|
const auto ret = DisplayConfigGetDeviceInfo_orig(requestPacket);
|
||||||
log_misc(
|
log_misc(
|
||||||
"popn",
|
"popn",
|
||||||
@@ -436,6 +585,12 @@ namespace games::popn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// monitor hook
|
// monitor hook
|
||||||
|
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 =
|
DisplayConfigGetDeviceInfo_orig =
|
||||||
detour::iat_try("DisplayConfigGetDeviceInfo",
|
detour::iat_try("DisplayConfigGetDeviceInfo",
|
||||||
DisplayConfigGetDeviceInfo_hook, avs::game::DLL_INSTANCE);
|
DisplayConfigGetDeviceInfo_hook, avs::game::DLL_INSTANCE);
|
||||||
@@ -473,9 +628,11 @@ 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
|
||||||
|
|
||||||
|
if (!GRAPHICS_WINDOWED) {
|
||||||
wintouchemu::FORCE = true;
|
wintouchemu::FORCE = true;
|
||||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
wintouchemu::hook_title_ends("", "Main Screen", avs::game::DLL_INSTANCE);
|
wintouchemu::hook_title_ends("", "Main Screen", avs::game::DLL_INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -463,7 +463,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::RegisterSoftwareDevice(void *pIniti
|
|||||||
}
|
}
|
||||||
|
|
||||||
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() {
|
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() {
|
||||||
return pReal->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) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
|
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
|
||||||
@@ -471,10 +484,26 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
|
|||||||
DWORD Flags,
|
DWORD Flags,
|
||||||
D3DADAPTER_IDENTIFIER9 *pIdentifier)
|
D3DADAPTER_IDENTIFIER9 *pIdentifier)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1 && pIdentifier) {
|
||||||
|
*pIdentifier = {};
|
||||||
|
strcpy(pIdentifier->DeviceName, "\\\\.\\DISPLAY_SPICE_FAKE");
|
||||||
|
log_misc(
|
||||||
|
"graphics::d3d9",
|
||||||
|
"GetAdapterIdentifier called for fake subscreen adapter 1: {}",
|
||||||
|
pIdentifier->DeviceName);
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
CHECK_RESULT(pReal->GetAdapterIdentifier(Adapter, Flags, pIdentifier));
|
CHECK_RESULT(pReal->GetAdapterIdentifier(Adapter, Flags, pIdentifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) {
|
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) {
|
||||||
|
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
|
||||||
|
log_misc("graphics::d3d9", "GetAdapterModeCount called for fake subscreen adapter");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
return pReal->GetAdapterModeCount(Adapter, Format);
|
return pReal->GetAdapterModeCount(Adapter, Format);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +513,19 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
|||||||
UINT Mode,
|
UINT Mode,
|
||||||
D3DDISPLAYMODE *pMode)
|
D3DDISPLAYMODE *pMode)
|
||||||
{
|
{
|
||||||
|
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
|
||||||
|
log_misc("graphics::d3d9", "EnumAdapterModes called for fake subscreen adapter");
|
||||||
|
if (Mode == 0 && pMode) {
|
||||||
|
pMode->Width = 1280;
|
||||||
|
pMode->Height = 800;
|
||||||
|
pMode->RefreshRate = 60;
|
||||||
|
pMode->Format = D3DFMT_X8R8G8B8;
|
||||||
|
return S_OK;
|
||||||
|
} else {
|
||||||
|
return D3DERR_INVALIDCALL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HRESULT ret = pReal->EnumAdapterModes(Adapter, Format, Mode, pMode);
|
HRESULT ret = pReal->EnumAdapterModes(Adapter, Format, Mode, pMode);
|
||||||
|
|
||||||
if (SUCCEEDED(ret) && pMode) {
|
if (SUCCEEDED(ret) && pMode) {
|
||||||
@@ -580,6 +622,11 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormat(
|
|||||||
D3DRESOURCETYPE RType,
|
D3DRESOURCETYPE RType,
|
||||||
D3DFORMAT CheckFormat)
|
D3DFORMAT CheckFormat)
|
||||||
{
|
{
|
||||||
|
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
|
||||||
|
log_misc("graphics::d3d9", "CheckDeviceFormat called for fake subscreen adapter");
|
||||||
|
return D3D_OK;
|
||||||
|
}
|
||||||
|
|
||||||
CHECK_RESULT(pReal->CheckDeviceFormat(Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat));
|
CHECK_RESULT(pReal->CheckDeviceFormat(Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -677,10 +724,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
|
|||||||
pCaps->NumberOfAdaptersInGroup = 1;
|
pCaps->NumberOfAdaptersInGroup = 1;
|
||||||
}
|
}
|
||||||
} else if (games::popn::is_pikapika_model()) {
|
} else if (games::popn::is_pikapika_model()) {
|
||||||
if (GRAPHICS_WINDOWED) {
|
|
||||||
pCaps->NumberOfAdaptersInGroup = 2;
|
pCaps->NumberOfAdaptersInGroup = 2;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ HWND TDJ_SUBSCREEN_WINDOW = nullptr;
|
|||||||
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
|
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
|
||||||
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
|
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
|
||||||
HWND POPN_SUBSCREEN_WINDOW = nullptr;
|
HWND POPN_SUBSCREEN_WINDOW = nullptr;
|
||||||
|
bool FAKE_SUBSCREEN_ADAPTER = false;
|
||||||
|
|
||||||
// icon
|
// icon
|
||||||
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
|
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ extern HWND POPN_SUBSCREEN_WINDOW;
|
|||||||
extern HWND GFDM_SUBSCREEN_WINDOW;
|
extern HWND GFDM_SUBSCREEN_WINDOW;
|
||||||
|
|
||||||
extern bool SUBSCREEN_FORCE_REDRAW;
|
extern bool SUBSCREEN_FORCE_REDRAW;
|
||||||
|
extern bool FAKE_SUBSCREEN_ADAPTER;
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
extern std::string GRAPHICS_DEVICEID;
|
extern std::string GRAPHICS_DEVICEID;
|
||||||
|
|||||||
@@ -559,7 +559,7 @@ namespace sysutils {
|
|||||||
// if there is one preferred by the user, use this one, and hide others
|
// if there is one preferred by the user, use this one, and hide others
|
||||||
log_info(
|
log_info(
|
||||||
"sysutils",
|
"sysutils",
|
||||||
"returning second monitor, {} @ index {} (-sysutilssubmonitor)",
|
"returning second monitor, {} @ index {} (-iidxsubmonitor / -sdvxsubmonitor)",
|
||||||
lpDisplayDevice->DeviceName,
|
lpDisplayDevice->DeviceName,
|
||||||
iDevNum);
|
iDevNum);
|
||||||
return result_orig;
|
return result_orig;
|
||||||
|
|||||||
Reference in New Issue
Block a user