Update to spice2x-25-04-25 (pre-apply)

> broken commit
This commit is contained in:
[ ]
2025-05-07 00:25:31 +09:00
parent 04dee88276
commit c94de456b5
543 changed files with 78491 additions and 91698 deletions
+227 -54
View File
@@ -12,6 +12,7 @@
#endif
#include "avs/game.h"
#include "cfg/screen_resize.h"
#include "games/iidx/iidx.h"
#include "games/sdvx/sdvx.h"
#include "games/io.h"
@@ -200,6 +201,34 @@ static std::string presentation_interval2s(UINT presentation_interval) {
FLAGS_END(presentation_interval);
}
static void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params);
static bool is_dx9_on_12_enabled() {
switch (GRAPHICS_9_ON_12_STATE) {
case DX9ON12_FORCE_OFF:
log_info("graphics::d3d9", "DirectX 9on12: forced OFF by user (-dx9on12)");
return false;
case DX9ON12_FORCE_ON:
log_info("graphics::d3d9", "DirectX 9on12: forced ON by user (-9on12 or -dx9on12)");
return true;
case DX9ON12_AUTO:
default:
if (GRAPHICS_9_ON_12_REQUESTED_BY_GAME) {
log_info(
"graphics::d3d9",
"DirectX 9on12: enabled automatically for current game (tip: use -dx9on12 to force on or off)");
return true;
} else {
log_info(
"graphics::d3d9",
"DirectX 9on12: disabled by default, using DX9 (tip: use -dx9on12 to force on or off)");
return false;
}
}
}
static IDirect3D9 *WINAPI Direct3DCreate9_hook(UINT SDKVersion) {
log_misc("graphics::d3d9", "Direct3DCreate9 hook hit");
@@ -214,7 +243,7 @@ static IDirect3D9 *WINAPI Direct3DCreate9_hook(UINT SDKVersion) {
// create interface
IDirect3D9 *value;
if (GRAPHICS_9_ON_12) {
if (is_dx9_on_12_enabled()) {
if (!Direct3DCreate9On12_orig) {
log_fatal("graphics::d3d9", "unable to find Direct3DCreate9On12");
} else {
@@ -247,7 +276,7 @@ static HRESULT WINAPI Direct3DCreate9Ex_hook(UINT SDKVersion, IDirect3D9Ex **d3d
// call original
HRESULT result;
if (GRAPHICS_9_ON_12) {
if (is_dx9_on_12_enabled()) {
if (!Direct3DCreate9On12Ex_orig) {
log_fatal("graphics::d3d9", "unable to find Direct3DCreate9On12Ex");
} else {
@@ -389,7 +418,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
{
HRESULT ret = pReal->EnumAdapterModes(Adapter, Format, Mode, pMode);
if (SUCCEEDED(ret) && pMode && avs::game::is_model("LDJ")) {
if (SUCCEEDED(ret) && pMode) {
/*
log_misc("graphics::d3d9", "IDirect3D9::EnumAdapterMode({}, {}, {}) => {}x{} @ {} Hz ({})",
Adapter,
@@ -406,52 +435,66 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
auto height = pMode->Height;
auto refresh = pMode->RefreshRate;
if (Mode == 0 && games::iidx::TDJ_MODE) {
if (games::iidx::is_tdj_fhd()) {
log_misc("graphics::d3d9", "overriding mode 0 to 1920x1080 @ 120 Hz (for TDJ FHD)");
pMode->Width = 1920;
pMode->Height = 1080;
pMode->RefreshRate = 120;
modified = true;
} else {
log_misc("graphics::d3d9", "overriding mode 0 to 1280x720 @ 120 Hz (for TDJ HD)");
pMode->Width = 1280;
pMode->Height = 720;
pMode->RefreshRate = 120;
if (avs::game::is_model("LDJ")) {
if (Mode == 0 && games::iidx::TDJ_MODE) {
if (games::iidx::is_tdj_fhd()) {
log_misc("graphics::d3d9", "overriding mode 0 to 1920x1080 @ 120 Hz (for TDJ FHD)");
pMode->Width = 1920;
pMode->Height = 1080;
pMode->RefreshRate = 120;
modified = true;
} else {
log_misc("graphics::d3d9", "overriding mode 0 to 1280x720 @ 120 Hz (for TDJ HD)");
pMode->Width = 1280;
pMode->Height = 720;
pMode->RefreshRate = 120;
modified = true;
}
}
// For whatever reason, TDJ FHD mode prefers to pick lower (~60Hz) resolutions instead
// of 1080p@120Hz. Remove them here and try to force 120+ Hz.
if (!modified && games::iidx::is_tdj_fhd() && refresh < 110) {
if ((width == 1920 && height == 1080) ||
(width == 1280 && height == 720)){
log_misc(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
modified = true;
}
}
// zero out display mode for bad entries
// - skip non-native resolutions
// - skip 75 and 90 Hz entries because LDJ game timing is messed up with it
// (TDJ should be fine as it assumes a fixed 60 Hz timing)
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
log_misc(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
modified = true;
}
}
// For whatever reason, TDJ FHD mode prefers to pick these resolutions over 1080p@120Hz.
// Remove them entirely.
if (!modified && games::iidx::is_tdj_fhd() && refresh == 60) {
if ((width == 1920 && height == 1080) ||
(width == 1280 && height == 720)){
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
log_misc(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-iidxforce720p)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
modified = true;
}
}
// zero out display mode for bad entries
// - skip non-native resolutions
// - skip 75 and 90 Hz entries because LDJ game timing is messed up with it
// (TDJ should be fine as it assumes a fixed 60 Hz timing)
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
log_misc(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
modified = true;
}
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
if (!modified &&
(width > height) &&
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW)) {
log_misc(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-sp2x-iidxforce720p)",
"graphics::d3d9", "swapping width and height for mode {}, {}x{} @ {}Hz (-autoorientation)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
pMode->Height = width;
pMode->Width = height;
modified = true;
}
}
@@ -551,15 +594,31 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
}
HRESULT ret = this->pReal->GetDeviceCaps(Adapter, DeviceType, pCaps);
// IIDX Lightning Cabinet & SDVX Valkyrie Cabinet
if (SUCCEEDED(ret) && !GRAPHICS_WINDOWED && avs::game::is_model({"LDJ", "KFC"})) {
pCaps->NumberOfAdaptersInGroup = 2;
if (FAILED(ret)) {
return ret;
}
if (SUCCEEDED(ret)) {
if (GRAPHICS_WINDOWED && (avs::game::is_model("NBT") || avs::game::is_model("PAN"))) {
if (avs::game::is_model("LDJ")) {
if (!GRAPHICS_WINDOWED) {
// use 2 so that the subscreen overlay can be drawn in full screen with single monitor
pCaps->NumberOfAdaptersInGroup = 2;
}
// in windowed mode, LDJ will always launch two windows, no special handling needed here
} else if (avs::game::is_model("KFC")) {
if (GRAPHICS_WINDOWED & GRAPHICS_PREVENT_SECONDARY_WINDOW) {
// user wants windowed mode but does not want subscreen at all
pCaps->NumberOfAdaptersInGroup = 1;
} else {
// in both full screen and windowed mode, use 2 so that the game draws the subscreen
// (if this is 1, the game won't even draw the second window, causing subscreen overlay to not work)
pCaps->NumberOfAdaptersInGroup = 2;
}
} else if (avs::game::is_model({"NBT", "PAN"})) {
// beatstream, nostalgia
if (GRAPHICS_WINDOWED) {
pCaps->NumberOfAdaptersInGroup = std::min(pCaps->NumberOfAdaptersInGroup, 1u);
} else if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
pCaps->NumberOfAdaptersInGroup = 1;
}
}
@@ -621,7 +680,29 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
// dump presentation parameters
for (size_t i = 0; i < num_adapters; i++) {
const auto *params = &pPresentationParameters[i];
auto *params = &pPresentationParameters[i];
if (!GRAPHICS_WINDOWED && i == 0) {
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
log_misc(
"graphics::d3d9",
"use custom resolution {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
log_misc(
"graphics::d3d9",
"swap full orientation {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
params->BackBufferHeight, params->BackBufferWidth);
std::swap(params->BackBufferWidth, params->BackBufferHeight);
}
}
log_info("graphics::d3d9",
"D3D9 presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
@@ -648,6 +729,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
if (GRAPHICS_WINDOWED) {
pPresentationParameters->Windowed = true;
pPresentationParameters->FullScreen_RefreshRateInHz = 0;
update_backbuffer_dimensions(pPresentationParameters);
} else if (GRAPHICS_FORCE_REFRESH > 0) {
log_info("graphics::d3d9", "force refresh rate: {} => {} Hz (-graphics-force-refresh option)",
@@ -672,6 +754,13 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
BehaviorFlags &= ~D3DCREATE_ADAPTERGROUP_DEVICE;
}
if (GRAPHICS_FORCE_VSYNC_BUFFER.has_value()) {
log_info("graphics::d3d9", "force BackBufferCount: {} => {}",
pPresentationParameters->BackBufferCount,
GRAPHICS_FORCE_VSYNC_BUFFER.value());
pPresentationParameters->BackBufferCount = GRAPHICS_FORCE_VSYNC_BUFFER.value();
}
// call original
HRESULT ret = this->pReal->CreateDevice(
Adapter,
@@ -686,7 +775,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
// log error
log_info("graphics::d3d9", "IDirect3D9::CreateDevice failed, hr={}", FMT_HRESULT(ret));
} else {
} else if (!D3D9_DEVICE_HOOK_DISABLE) {
graphics_hook_window(hFocusWindow, pPresentationParameters);
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
@@ -736,7 +825,13 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
{
assert(is_d3d9ex);
log_misc("graphics::d3d9", "IDirect3D9Ex::CreateDeviceEx hook hit");
log_misc("graphics::d3d9", "IDirect3D9Ex::CreateDeviceEx hook hit ({}, {}, {}, {}, {}, {})",
Adapter,
DeviceType,
fmt::ptr(hFocusWindow),
behavior2s(BehaviorFlags),
fmt::ptr(pPresentationParameters),
fmt::ptr(ppReturnedDeviceInterface));
// check parameters
if (!pPresentationParameters || !ppReturnedDeviceInterface) {
@@ -773,10 +868,32 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
}
for (size_t i = 0; i < num_adapters; i++) {
const auto *params = &pPresentationParameters[i];
auto *params = &pPresentationParameters[i];
if (!GRAPHICS_WINDOWED && i == 0) {
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
log_misc(
"graphics::d3d9",
"use custom resolution {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
log_misc(
"graphics::d3d9",
"swap full orientation {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
params->BackBufferHeight, params->BackBufferWidth);
std::swap(params->BackBufferWidth, params->BackBufferHeight);
}
}
log_info("graphics::d3d9",
"D3D9 presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
"D3D9Ex presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
"Format: {}, BackBufferCount: {}, MultiSampleType: {}, MultiSampleQuality: {}, "
"SwapEffect: {}, Windowed: {}, EnableAutoDepthStencil: {}, AutoDepthStencilFormat: {}, "
"Flags: {}, FullScreen_RefreshRateInHz: {}, PresentationInterval: {}",
@@ -797,7 +914,16 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
}
if (pFullscreenDisplayMode) {
for (size_t i = 0; i < num_adapters; i++) {
const auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
if (!GRAPHICS_WINDOWED && i == 0) {
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
}
}
log_info("graphics::d3d9",
"D3D9Ex fullscreen display mode for adapter {}: Width: {}, Height: {}, RefreshRate: {}, "
@@ -824,6 +950,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
pPresentationParameters->Windowed = true;
pPresentationParameters->FullScreen_RefreshRateInHz = 0;
update_backbuffer_dimensions(pPresentationParameters);
pFullscreenDisplayMode = nullptr;
} else if (GRAPHICS_FORCE_REFRESH > 0) {
log_info("graphics::d3d9", "force refresh rate: {} => {} Hz",
@@ -845,6 +972,13 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
BehaviorFlags &= ~D3DCREATE_ADAPTERGROUP_DEVICE;
}
if (GRAPHICS_FORCE_VSYNC_BUFFER.has_value()) {
log_info("graphics::d3d9", "force BackBufferCount: {} => {}",
pPresentationParameters->BackBufferCount,
GRAPHICS_FORCE_VSYNC_BUFFER.value());
pPresentationParameters->BackBufferCount = GRAPHICS_FORCE_VSYNC_BUFFER.value();
}
// call original
HRESULT result = static_cast<IDirect3D9Ex *>(this->pReal)->CreateDeviceEx(
Adapter,
@@ -860,7 +994,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
// log error
log_warning("graphics::d3d9", "CreateDeviceEx failed, hr={}", FMT_HRESULT(result));
} else {
} else if (!D3D9_DEVICE_HOOK_DISABLE) {
graphics_hook_window(hFocusWindow, pPresentationParameters);
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
@@ -950,13 +1084,11 @@ IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen() {
static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
if (!ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE && SUB_SWAP_CHAIN == nullptr) {
ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = true;
HRESULT hr = wrapped_device->GetSwapChain(1, &SUB_SWAP_CHAIN);
if (FAILED(hr)) {
log_warning(
"graphics::d3d9",
"failed to acquire sub screeen swap chain; "
"this can be ignored if game does not have sub screen, hr={}",
"failed to acquire sub screeen swap chain! hr={}",
FMT_HRESULT(hr));
return;
}
@@ -1160,7 +1292,12 @@ void graphics_d3d9_on_present(
device->EndScene();
}
if (avs::game::is_model({"LDJ", "KFC"})) {
// for IIDX TDJ / SDVX UFC, handle subscreen
const bool is_vm =
avs::game::is_model("KFC") &&
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H');
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
if (is_vm || is_tdj) {
graphics_d3d9_ldj_on_present(wrapped_device);
}
@@ -1282,3 +1419,39 @@ void graphics_d3d9_on_present(
}
}
}
void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params) {
if (!GRAPHICS_WINDOW_BACKBUFFER_SCALE) {
return;
}
// only do this for primary adapter
static bool first_adapter_hooked = false;
if (first_adapter_hooked) {
return;
}
if (GRAPHICS_WINDOW_SIZE.has_value()) {
first_adapter_hooked = true;
params->BackBufferWidth = GRAPHICS_WINDOW_SIZE.value().first;
params->BackBufferHeight = GRAPHICS_WINDOW_SIZE.value().second;
log_info(
"graphics::d3d9",
"override BackBufferWidth / BackBufferHeight with {}x{} (from -windowresize)",
params->BackBufferWidth,
params->BackBufferHeight);
return;
}
if (cfg::SCREENRESIZE->client_width != 0 && cfg::SCREENRESIZE->client_height != 0) {
first_adapter_hooked = true;
params->BackBufferWidth = cfg::SCREENRESIZE->client_width;
params->BackBufferHeight = cfg::SCREENRESIZE->client_height;
log_info(
"graphics::d3d9",
"override BackBufferWidth / BackBufferHeight with {}x{} (from screen resize config file)",
params->BackBufferWidth,
params->BackBufferHeight);
return;
}
}
+135 -41
View File
@@ -7,6 +7,7 @@
#include "hooks/graphics/graphics.h"
#include "overlay/overlay.h"
#include "util/flags_helper.h"
#include "util/utils.h"
#include "cfg/screen_resize.h"
#include "d3d9_backend.h"
@@ -692,88 +693,181 @@ static IDirect3DSurface9 *backbuffer = nullptr;
static LPDIRECT3DSWAPCHAIN9 mSwapChain = nullptr;
static IDirect3DTexture9* tex;
static UINT topSurface_width = 0;
static UINT topSurface_height = 0;
static float topSurface_aspect_ratio = 1.f;
void SurfaceHook(IDirect3DDevice9 *pReal) {
// log_misc("graphics::d3d9", "SurfaceHook called");
D3DPRESENT_PARAMETERS param {};
pReal->GetSwapChain(0, &mSwapChain);
mSwapChain->GetPresentParameters(&param);
// phase 0 - create a surface that has the same aspect ratio as the original back buffer
// but larger in size. this is needed to ensure that when the user zooms out, we have
// sufficient buffer space (black border) around the original image
//
// (only done once)
if (!topSurface) {
if (pReal->CreateTexture(4096, 4096, 1,
topSurface_width = param.BackBufferWidth * 3;
topSurface_height = param.BackBufferHeight * 3;
topSurface_aspect_ratio = (float)topSurface_width / (float)topSurface_height;
if (pReal->CreateTexture(topSurface_width, topSurface_height, 1,
D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8,
D3DPOOL_DEFAULT, &tex, NULL) != D3D_OK) {
log_misc("graphics::d3d9", "create texture failed");
log_warning("graphics::d3d9", "SurfaceHook - create texture failed");
}
log_misc("graphics::d3d9", "Backbuffer: {} {} {}",
param.BackBufferWidth, param.BackBufferHeight, param.BackBufferCount);
tex->GetSurfaceLevel(0, &topSurface);
log_misc(
"graphics::d3d9", "SurfaceHook - Backbuffer: {} {} {}",
param.BackBufferWidth, param.BackBufferHeight, param.BackBufferCount);
tex->GetSurfaceLevel(0, &topSurface);
if (mSwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backbuffer) != D3D_OK) {
log_misc("graphics::d3d9", "GetBackBuffer failed");
log_warning("graphics::d3d9", "SurfaceHook - GetBackBuffer failed");
}
}
const int rectLeft = 1024;
const int rectTop = 576;
// pre-calculate dimensions used for phase 1
const int rectLeft = param.BackBufferWidth;
const int rectTop = param.BackBufferHeight;
const int w = param.BackBufferWidth;
const int h = param.BackBufferHeight;
D3DLOCKED_RECT rect;
HRESULT hr;
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
// phase 1 - copy the original back buffer onto the new surface.
// we draw it 1:1 in the center of the surface.
// if orientation is swapped, fix up the rect and draw it in the center.
//
// for this phase we always render with the same rect so that it's always overwritten
// by a new frame on the same spot.
RECT originRect {
rectLeft,
rectTop,
(LONG)(rectLeft + w),
(LONG)(rectTop + h),
};
if (GRAPHICS_FS_ORIENTATION_SWAP) {
originRect.left = (topSurface_width / 2) - (GRAPHICS_FS_ORIGINAL_WIDTH / 2);
originRect.top = (topSurface_height / 2) - (GRAPHICS_FS_ORIGINAL_HEIGHT / 2);
originRect.right = originRect.left + GRAPHICS_FS_ORIGINAL_WIDTH;
originRect.bottom = originRect.top + GRAPHICS_FS_ORIGINAL_HEIGHT;
}
// log_misc(
// "graphics::d3d9", "originRect: {} {} {} {}",
// originRect.left, originRect.top, originRect.right, originRect.bottom);
hr = pReal->StretchRect(
backbuffer, nullptr,
topSurface, &originRect,
D3DTEXF_LINEAR);
if (hr != D3D_OK) {
log_warning(
"graphics::d3d9",
"SurfaceHook - StretchRect backbuffer failed, rect: {} {} {} {}",
originRect.left, originRect.top, originRect.right, originRect.bottom);
}
topSurface->UnlockRect();
// phase 2 - viewport calculation - do the actual zoom / offset math
// figure out what region of the surface to copy back to the back buffer.
RECT targetRect {
rectLeft,
rectTop,
(LONG)(rectLeft + w),
(LONG)(rectTop + h),
};
if (cfg::SCREENRESIZE->enable_screen_resize) {
auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene];
auto w_new = w / scene.scale_x;
auto h_new = h / scene.scale_y;
// stretch to add top/left offset to avoid going negative
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
auto hr = pReal->StretchRect(
backbuffer, nullptr,
topSurface, &targetRect,
D3DTEXF_LINEAR);
// make sure the scaling is within bounds
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
if (w_new > topSurface_width || h_new > topSurface_height) {
w_new = topSurface_width;
h_new = topSurface_height;
}
} else {
w_new = MIN(w_new, topSurface_width);
h_new = MIN(h_new, topSurface_height);
}
if (hr != D3D_OK) {
log_misc("graphics::d3d9", "StretchRect backbuffer failed");
}
topSurface->UnlockRect();
targetRect.left = (topSurface_width / 2) - (w_new / 2) - scene.offset_x;
targetRect.top = (topSurface_height / 2) - (h_new / 2) - scene.offset_y;
targetRect.right = targetRect.left + w_new;
targetRect.bottom = targetRect.top + h_new;
// do the actual zoom / offset math
if (cfg::SCREENRESIZE->centered) {
targetRect.right = (w + rectLeft) / cfg::SCREENRESIZE->scale_x;
targetRect.bottom = (h + rectTop) / cfg::SCREENRESIZE->scale_y;
const LONG deltaH = ((targetRect.bottom - targetRect.top) - h) / 2;
const LONG deltaW = ((targetRect.right - targetRect.left) - w) / 2;
targetRect.top -= deltaH;
targetRect.bottom -= deltaH;
targetRect.left -= deltaW;
targetRect.right -= deltaW;
} else {
targetRect.left -= cfg::SCREENRESIZE->offset_x;
targetRect.top += cfg::SCREENRESIZE->offset_y;
targetRect.right = -cfg::SCREENRESIZE->offset_x;
targetRect.right += (w + rectLeft) / cfg::SCREENRESIZE->scale_x;
targetRect.bottom = cfg::SCREENRESIZE->offset_y;
targetRect.bottom += (h + rectTop) / cfg::SCREENRESIZE->scale_y;
// boundary check to prevent StretchRect failures
if (targetRect.left < 0) {
targetRect.left = 0;
targetRect.right = w_new;
} else if (targetRect.right > (LONG)topSurface_width) {
targetRect.left = topSurface_width - w_new;
targetRect.right = topSurface_width;
}
if (targetRect.top < 0) {
targetRect.top = 0;
targetRect.bottom = h_new;
} else if (targetRect.bottom > (LONG)topSurface_height) {
targetRect.top = topSurface_height - h_new;
targetRect.bottom = topSurface_height;
}
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
// increase the viewport to fit the image on screen (effectively, zoom out a little)
if (GRAPHICS_FS_ORIGINAL_WIDTH < GRAPHICS_FS_ORIGINAL_HEIGHT) {
// portrait game on landscape display
// height should match the original image
targetRect.top = originRect.top;
targetRect.bottom = originRect.bottom;
// width of viewport should be wider, effectively shrinking the image on x-axis when rendered
const auto w_new = GRAPHICS_FS_ORIGINAL_HEIGHT * topSurface_aspect_ratio;
targetRect.left = (topSurface_width / 2) - (w_new / 2);
targetRect.right = targetRect.left + w_new;
} else {
// landscape game on portrait display
targetRect.left = originRect.left;
targetRect.right = originRect.right;
// height of viewport should be taller, effectively shrinking the image on y-axis when rendered
const auto h_new = GRAPHICS_FS_ORIGINAL_WIDTH / topSurface_aspect_ratio;
targetRect.top = (topSurface_height / 2) - (h_new / 2);
targetRect.bottom = targetRect.top + h_new;
}
}
// draw to back buffer
// log_misc("graphics::d3d9", "targetRect: {} {} {} {}",
// targetRect.left, targetRect.top, targetRect.right, targetRect.bottom);
// phase 3 - draw the surface to back buffer
backbuffer->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
bool use_linear_filter = true;
if (cfg::SCREENRESIZE->enable_screen_resize) {
use_linear_filter = cfg::SCREENRESIZE->enable_linear_filter;
}
hr = pReal->StretchRect(
topSurface, &targetRect,
backbuffer, nullptr,
cfg::SCREENRESIZE->enable_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
use_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
backbuffer->UnlockRect();
if (hr != D3D_OK) {
log_misc("graphics::d3d9", "StretchRect targetRect failed");
log_warning(
"graphics::d3d9",
"SurfaceHook - StretchRect targetRect failed, you must reset image scaling to default values! rect: {} {} {} {}",
targetRect.left, targetRect.top, targetRect.right, targetRect.bottom);
}
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::EndScene() {
WRAP_DEBUG;
if (cfg::SCREENRESIZE->enable_screen_resize) {
if (cfg::SCREENRESIZE->enable_screen_resize || GRAPHICS_FS_ORIENTATION_SWAP) {
SurfaceHook(pReal);
}
+198 -68
View File
@@ -11,16 +11,19 @@
#include "cfg/icon.h"
#include "cfg/screen_resize.h"
#include "games/ddr/ddr.h"
#include "games/iidx/iidx.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "launcher/shutdown.h"
#include "overlay/overlay.h"
#include "touch/touch.h"
#include "touch/touch_indicators.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/fileutils.h"
#include "util/utils.h"
#include "misc/wintouchemu.h"
#include "util/time.h"
#include "rawinput/rawinput.h"
struct CaptureData {
std::shared_ptr<uint8_t[]> data;
@@ -28,11 +31,15 @@ struct CaptureData {
uint64_t timestamp;
};
HWND TDJ_SUBSCREEN_WINDOW = nullptr;
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
// icon
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
// state
static WNDPROC WNDPROC_ORIG = nullptr;
static WNDPROC WSUB_WNDPROC_ORIG = nullptr;
static std::vector<WNDPROC> WNDPROC_CUSTOM {};
static bool GRAPHICS_SCREENSHOT_TRIGGER = false;
static std::set<int> GRAPHICS_SCREENS { 0 };
@@ -53,9 +60,17 @@ graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
bool GRAPHICS_WINDOWED = false;
std::vector<HWND> GRAPHICS_WINDOWS;
UINT GRAPHICS_FORCE_REFRESH = 0;
std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
bool GRAPHICS_9_ON_12 = false;
bool GRAPHICS_PREVENT_SECONDARY_WINDOW = false;
graphics_dx9on12_state GRAPHICS_9_ON_12_STATE = DX9ON12_AUTO;
bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME = false;
bool SUBSCREEN_FORCE_REDRAW = false;
bool D3D9_DEVICE_HOOK_DISABLE = false;
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
bool GRAPHICS_FS_ORIENTATION_SWAP = false;
uint32_t GRAPHICS_FS_ORIGINAL_WIDTH = 0;
uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT = 0;
// settings
std::string GRAPHICS_DEVICEID = "PCI\\VEN_1002&DEV_7146";
@@ -154,26 +169,44 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
// capture mouse
if (GRAPHICS_CAPTURE_CURSOR) {
bool free_cursor = false;
bool capture_cursor = false;
bool early_return = false;
switch (uMsg) {
case WM_SETFOCUS: {
// capture mouse
RECT WINDOW_RECT;
GetWindowRect(hWnd, &WINDOW_RECT);
ClipCursor(&WINDOW_RECT);
return true;
}
case WM_KILLFOCUS: {
// free mouse
ClipCursor(0);
return true;
}
case WM_SETFOCUS:
capture_cursor = true;
early_return = true;
break;
case WM_KILLFOCUS:
free_cursor = true;
early_return = true;
break;
case WM_WINDOWPOSCHANGED:
// known issue: dragging with the title bar results in WM_WINDOWPOSCHANGED
// getting called, which calls ClipCursor successfully, but doesn't actually
// confine the cursor for some reason; seems like odd Windows behavior
// (can be fixed if focus is shifted to another window and then back to the game
// window)
if (hWnd == GetActiveWindow()) {
capture_cursor = true;
} else {
free_cursor = true;
}
// do not return early; may result in WM_SIZE / WM_MOVE no longer being called
default:
break;
}
if (free_cursor) {
ClipCursor(nullptr);
} else if (capture_cursor) {
RECT WINDOW_RECT;
GetWindowRect(hWnd, &WINDOW_RECT);
ClipCursor(&WINDOW_RECT);
}
if (early_return) {
return true;
}
}
// drop keydown messages
@@ -187,47 +220,44 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
break;
}
// update the size of an emulated touch window is the window changes size or moves
if (SPICETOUCH_TOUCH_HWND) {
switch (uMsg) {
case WM_MOVE:
case WM_SIZE: {
// get window rect
RECT windowRect {};
GetWindowRect(hWnd, &windowRect);
// adjust to client area
POINT p {};
ClientToScreen(hWnd, &p);
windowRect.top = p.y;
int new_x = windowRect.left;
int new_y = windowRect.top;
int new_width = windowRect.right - new_x;
int new_height = windowRect.bottom - new_y;
log_misc("graphics", "detected window change ({}x{} @ {}, {} -> {}x{} @ {}, {}), updating touch window to match",
SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT, SPICETOUCH_TOUCH_X, SPICETOUCH_TOUCH_Y,
new_width, new_height, new_x, new_y);
SPICETOUCH_TOUCH_X = new_x;
SPICETOUCH_TOUCH_Y = new_y;
SPICETOUCH_TOUCH_WIDTH = new_width;
SPICETOUCH_TOUCH_HEIGHT = new_height;
SetWindowPos(SPICETOUCH_TOUCH_HWND, HWND_TOP,
new_x, new_y,
new_width, new_height,
SWP_NOZORDER | SWP_NOREDRAW | SWP_NOREPOSITION | SWP_NOACTIVATE);
switch (uMsg) {
case WM_MOVE:
case WM_SIZE: {
// Update SPICETOUCH space when the main window changes size or moves.
// The update happens regardless of whether the "fake" spicetouch window is present or not.
// This allows touches received on subscreen window to be translated correctly.
update_spicetouch_window_dimensions(hWnd);
// log_misc(
// "graphics", "detected window change ({}x{} @ {}, {}), updating touch coord-space to match",
// SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT, SPICETOUCH_TOUCH_X, SPICETOUCH_TOUCH_Y);
// Update SPICETOUCH window if present
if (SPICETOUCH_TOUCH_HWND) {
SetWindowPos(
SPICETOUCH_TOUCH_HWND, HWND_TOP,
SPICETOUCH_TOUCH_X, SPICETOUCH_TOUCH_Y,
SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT,
SWP_NOZORDER | SWP_NOREDRAW | SWP_NOREPOSITION | SWP_NOACTIVATE);
}
default:
break;
}
default:
break;
}
// call default
return CallWindowProcA(WNDPROC_ORIG, hWnd, uMsg, wParam, lParam);
}
// window procedure for subscreen
// this might be replaced by spicetouch hook later
static LRESULT CALLBACK WsubWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (uMsg == WM_CLOSE) {
log_misc("graphics", "ignore WM_CLOSE for subscreen window");
return false;
}
return CallWindowProcA(WSUB_WNDPROC_ORIG, hWnd, uMsg, wParam, lParam);
}
static LONG WINAPI ChangeDisplaySettingsA_hook(DEVMODEA *lpDevMode, DWORD dwflags) {
log_misc("graphics", "ChangeDisplaySettingsA hook hit");
@@ -271,10 +301,12 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
LPVOID lpParam)
{
const std::string window_name(lpWindowName != nullptr ? lpWindowName : "(null)");
log_misc("graphics", "CreateWindowExA hook hit (0x{:08x}, {}, {}, 0x{:08x}, {}, {}, {}, {}, {}, {}, {}, {})",
dwExStyle,
fmt::ptr(lpClassName),
lpWindowName != nullptr ? lpWindowName : "(null)",
window_name,
dwStyle,
x,
y,
@@ -285,43 +317,86 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
fmt::ptr(hInstance),
fmt::ptr(lpParam));
// set display orientation
// only do this when the target window is portrait
// set display orientation and/or refresh rate
// only set orientation when the target window is portrait
// (avoid doing this for SDVX subscreen which is in landscape, for example)
if ((nHeight > nWidth) &&
const auto adjust_orientation =
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW)) {
DWORD orientation = (GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW) ? DMDO_90 : DMDO_270;
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW);
if ((nHeight > nWidth && adjust_orientation) || GRAPHICS_FORCE_REFRESH > 0) {
DEVMODE mode {};
// get display settings
if (EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &mode)) {
log_misc("graphics", "auto-rotate: rotating display to DMDO_xx mode {}", orientation);
mode.dmPelsWidth = nWidth;
mode.dmPelsHeight = nHeight;
mode.dmDisplayOrientation = orientation;
if (EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &mode)) {
if (adjust_orientation) {
DWORD orientation = GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ? DMDO_90 : DMDO_270;
log_misc(
"graphics",
"auto-rotate: call ChangeDisplaySettings and rotate display to DMDO_xx mode {}",
orientation);
mode.dmPelsWidth = nWidth;
mode.dmPelsHeight = nHeight;
mode.dmDisplayOrientation = orientation;
}
if (GRAPHICS_FORCE_REFRESH > 0) {
log_info(
"graphics",
"auto-rotate: force refresh rate: {} => {} Hz",
"call ChangeDisplaySettings to force refresh rate: {} => {} Hz (-graphics-force-refresh)",
mode.dmDisplayFrequency,
GRAPHICS_FORCE_REFRESH);
mode.dmDisplayFrequency = GRAPHICS_FORCE_REFRESH;
}
auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
const auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
if (disp_res != DISP_CHANGE_SUCCESSFUL) {
log_warning("graphics", "auto-rotate: failed to change display settings: {}", disp_res);
log_warning("graphics", "failed to change display settings: {}", disp_res);
}
} else {
log_warning("graphics", "auto-rotate: failed to get display settings");
log_warning("graphics", "failed to get display settings");
}
}
// gfdm
if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
// set window name
if (!lpWindowName) {
lpWindowName = "GITADORA";
}
}
bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub");
bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen");
// TDJ windowed mode with subscreen: hide maximize button
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window) {
dwStyle &= ~(WS_MAXIMIZEBOX);
}
// call original
HWND result = CreateWindowExA_orig(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
hWndParent, hMenu, hInstance, lpParam);
GRAPHICS_WINDOWS.push_back(result);
if (is_tdj_sub_window) {
// TDJ windowed mode: remember the subscreen window handle for later
TDJ_SUBSCREEN_WINDOW = result;
// hook for preventing the closing of subscreen window
if (GRAPHICS_IIDX_WSUB) {
graphics_hook_subscreen_window(TDJ_SUBSCREEN_WINDOW);
}
}
// hook for preventing the closing of subscreen window
if (is_sdvx_sub_window) {
SDVX_SUBSCREEN_WINDOW = result;
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
}
disable_touch_indicators(result);
return result;
}
@@ -387,6 +462,8 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
hWndParent, hMenu, hInstance, lpParam);
GRAPHICS_WINDOWS.push_back(result);
disable_touch_indicators(result);
return result;
}
@@ -400,10 +477,17 @@ static BOOL WINAPI EnumDisplayDevicesA_hook(LPCTSTR lpDevice, DWORD iDevNum,
// call original
BOOL value = EnumDisplayDevicesA_orig(lpDevice, iDevNum, lpDisplayDevice, dwFlags);
// device ID override
if (value) {
#ifndef SPICE64
// older IIDX games check for hardcoded PCI vendor/device ID pair of GPU
if ((avs::game::is_model("JDZ") || avs::game::is_model("KDZ")) && value) {
log_info(
"graphics",
"EnumDisplayDevicesA_hook: swap DeviceID {} with {} (for IIDX 18/19)",
lpDisplayDevice->DeviceID,
GRAPHICS_DEVICEID.c_str());
memcpy(&lpDisplayDevice->DeviceID, GRAPHICS_DEVICEID.c_str(), GRAPHICS_DEVICEID.size() + 1);
}
#endif
// return original result
return value;
@@ -434,6 +518,39 @@ static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHei
nHeight = rect.bottom - rect.top;
}
// iidx windowed TDJ mode
if (GRAPHICS_WINDOWED && TDJ_SUBSCREEN_WINDOW && hWnd == TDJ_SUBSCREEN_WINDOW) {
if (GRAPHICS_IIDX_WSUB) {
// (Experimental) Show subscreen in windowed mode
graphics_load_windowed_subscreen_parameters();
RECT rect {};
DWORD dwStyle;
dwStyle = GetWindowLongA(hWnd, GWL_STYLE);
SetRect(&rect, 0, 0, GRAPHICS_IIDX_WSUB_WIDTH, GRAPHICS_IIDX_WSUB_HEIGHT);
AdjustWindowRect(&rect, dwStyle, 0);
X = GRAPHICS_IIDX_WSUB_X;
Y = GRAPHICS_IIDX_WSUB_Y;
nWidth = rect.right - rect.left;
nHeight = rect.bottom - rect.top;
touch_attach_wnd(TDJ_SUBSCREEN_WINDOW);
} else {
// Existing behaviour: suppress subscreen window and prompt user to use overlay instead
log_info(
"graphics",
"MoveWindow hook - hiding TDJ subscreen window {}; please use subscreen overlay instead (-iidxtdjw)",
fmt::ptr(hWnd));
SendMessage(hWnd, WM_CLOSE, 0, 0);
TDJ_SUBSCREEN_WINDOW = nullptr;
return TRUE;
}
}
// call original
return MoveWindow_orig(hWnd, X, Y, nWidth, nHeight, bRepaint);
}
@@ -720,7 +837,12 @@ void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParamet
if (WNDPROC_ORIG == nullptr) {
WNDPROC_ORIG = reinterpret_cast<WNDPROC>(GetWindowLongPtrA(hWnd, GWLP_WNDPROC));
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WindowProc));
overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = true;
// NOLEGACY causes WM_CHAR to be not received
// reflec beat game engine does not pass WM_CHAR through for some reason (unrelated to SpiceTouch)
if (!rawinput::NOLEGACY && !(avs::game::is_model({"KBR", "LBR", "MBR"}))) {
overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = true;
}
graphics_capture_initial_window(hWnd);
}
}
@@ -737,6 +859,14 @@ void graphics_remove_wnd_proc(WNDPROC wndProc) {
}
}
void graphics_hook_subscreen_window(HWND hWnd) {
// hook window procedure
if (WSUB_WNDPROC_ORIG == nullptr) {
WSUB_WNDPROC_ORIG = reinterpret_cast<WNDPROC>(GetWindowLongPtrA(hWnd, GWLP_WNDPROC));
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WsubWindowProc));
}
}
void graphics_screens_register(int screen) {
std::lock_guard<std::mutex> lock(GRAPHICS_SCREENS_M);
+31 -2
View File
@@ -3,6 +3,7 @@
#include <string>
#include <vector>
#include <optional>
#include <cstdint>
#include <windows.h>
#include <d3d9.h>
@@ -16,6 +17,12 @@ enum graphics_orientation {
ORIENTATION_NORMAL = 2,
};
enum graphics_dx9on12_state {
DX9ON12_AUTO,
DX9ON12_FORCE_OFF,
DX9ON12_FORCE_ON,
};
// flag settings
extern bool GRAPHICS_CAPTURE_CURSOR;
extern bool GRAPHICS_LOG_HRESULT;
@@ -25,13 +32,32 @@ extern bool GRAPHICS_WINDOWED;
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
extern std::vector<HWND> GRAPHICS_WINDOWS;
extern UINT GRAPHICS_FORCE_REFRESH;
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
extern bool GRAPHICS_9_ON_12;
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
extern bool GRAPHICS_FS_ORIENTATION_SWAP;
extern uint32_t GRAPHICS_FS_ORIGINAL_WIDTH;
extern uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT;
extern graphics_dx9on12_state GRAPHICS_9_ON_12_STATE;
extern bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME;
extern std::optional<int> GRAPHICS_WINDOW_STYLE;
extern std::optional<std::string> GRAPHICS_WINDOW_SIZE;
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
extern std::optional<std::string> GRAPHICS_WINDOW_POS;
extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP;
extern bool GRAPHICS_WINDOW_BACKBUFFER_SCALE;
extern bool GRAPHICS_IIDX_WSUB;
extern std::optional<std::string> GRAPHICS_IIDX_WSUB_SIZE;
extern std::optional<std::string> GRAPHICS_IIDX_WSUB_POS;
extern int GRAPHICS_IIDX_WSUB_WIDTH;
extern int GRAPHICS_IIDX_WSUB_HEIGHT;
extern int GRAPHICS_IIDX_WSUB_X;
extern int GRAPHICS_IIDX_WSUB_Y;
extern HWND TDJ_SUBSCREEN_WINDOW;
extern HWND SDVX_SUBSCREEN_WINDOW;
extern bool SUBSCREEN_FORCE_REDRAW;
@@ -42,11 +68,13 @@ extern std::string GRAPHICS_SCREENSHOT_DIR;
// Direct3D 9 settings
extern std::optional<UINT> D3D9_ADAPTER;
extern DWORD D3D9_BEHAVIOR_DISABLE;
extern bool D3D9_DEVICE_HOOK_DISABLE;
void graphics_init();
void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParameters);
void graphics_add_wnd_proc(WNDPROC wndProc);
void graphics_remove_wnd_proc(WNDPROC wndProc);
void graphics_hook_subscreen_window(HWND hWnd);
void graphics_screens_register(int screen);
void graphics_screens_unregister(int screen);
void graphics_screens_get(std::vector<int> &screens);
@@ -69,3 +97,4 @@ void graphics_update_window_style(HWND hWnd);
void graphics_update_z_order(HWND hWnd);
void graphics_move_resize_window(HWND hWnd);
bool graphics_window_change_crashes_game();
void graphics_load_windowed_subscreen_parameters();
+60 -23
View File
@@ -5,6 +5,7 @@
#include "overlay/overlay.h"
#include "util/logging.h"
#include "util/utils.h"
#include "touch/touch.h"
#if 0
#define log_debug(module, format_str, ...) logger::push( \
@@ -18,9 +19,19 @@ void graphics_wm_style_changed(HWND hWnd, bool changed);
void graphics_wm_sizing_aspect_ratio(int edge, RECT& rect);
std::optional<int> GRAPHICS_WINDOW_STYLE;
std::optional<std::string> GRAPHICS_WINDOW_SIZE;
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
std::optional<std::string> GRAPHICS_WINDOW_POS;
bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false;
bool GRAPHICS_WINDOW_BACKBUFFER_SCALE = false;
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
bool GRAPHICS_IIDX_WSUB = false;
std::optional<std::string> GRAPHICS_IIDX_WSUB_SIZE;
std::optional<std::string> GRAPHICS_IIDX_WSUB_POS;
int GRAPHICS_IIDX_WSUB_WIDTH = 1280;
int GRAPHICS_IIDX_WSUB_HEIGHT = 720;
int GRAPHICS_IIDX_WSUB_X = 0;
int GRAPHICS_IIDX_WSUB_Y = 0;
// these flags are carefully constructed to ensure maximum compatibility
// (e.g., DDR likes to hang when SetWindowPos is called with certain params)
@@ -100,6 +111,9 @@ void graphics_capture_initial_window(HWND hWnd) {
graphics_update_z_order(hWnd);
}
// ensure spicetouch coordinates are initialized
update_spicetouch_window_dimensions(hWnd);
log_debug("graphics-windowed", "graphics_capture_initial_window returned");
}
@@ -109,10 +123,6 @@ void graphics_load_windowed_parameters() {
}
log_debug("graphics-windowed", "graphics_load_windowed_parameters called");
const auto remove_spaces = [](const char& c) {
return c == ' ';
};
if (GRAPHICS_WINDOW_STYLE.has_value()) {
log_debug(
"graphics-windowed",
@@ -124,32 +134,23 @@ void graphics_load_windowed_parameters() {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_SIZE");
uint32_t w, h;
auto s = GRAPHICS_WINDOW_SIZE.value();
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
if (sscanf(s.c_str(), "%u,%u", &w, &h) == 2) {
cfg::SCREENRESIZE->enable_window_resize = true;
cfg::SCREENRESIZE->client_keep_aspect_ratio = false;
cfg::SCREENRESIZE->client_width = w;
cfg::SCREENRESIZE->client_height = h;
} else {
log_warning("graphics-windowed", "failed to parse -sp2x-windowsize");
}
cfg::SCREENRESIZE->enable_window_resize = true;
cfg::SCREENRESIZE->client_keep_aspect_ratio = false;
cfg::SCREENRESIZE->client_width = GRAPHICS_WINDOW_SIZE.value().first;
cfg::SCREENRESIZE->client_height = GRAPHICS_WINDOW_SIZE.value().second;
}
if (GRAPHICS_WINDOW_POS.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_POS");
int32_t x, y;
auto s = GRAPHICS_WINDOW_POS.value();
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
if (sscanf(s.c_str(), "%d,%d", &x, &y) == 2) {
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(GRAPHICS_WINDOW_POS.value(), result)) {
cfg::SCREENRESIZE->enable_window_resize = true;
cfg::SCREENRESIZE->window_offset_x = x;
cfg::SCREENRESIZE->window_offset_y = y;
cfg::SCREENRESIZE->window_offset_x = result.first;
cfg::SCREENRESIZE->window_offset_y = result.second;
} else {
log_warning("graphics-windowed", "failed to parse -sp2x-windowpos");
log_warning("graphics-windowed", "failed to parse -windowpos");
}
}
@@ -161,6 +162,42 @@ void graphics_load_windowed_parameters() {
log_debug("graphics-windowed", "graphics_load_windowed_parameters returned");
}
void graphics_load_windowed_subscreen_parameters() {
if (!GRAPHICS_WINDOWED) {
return;
}
log_debug("graphics-windowed", "graphics_load_windowed_subscreen_parameters called");
if (GRAPHICS_IIDX_WSUB_SIZE.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_SIZE");
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(GRAPHICS_IIDX_WSUB_SIZE.value(), result)) {
GRAPHICS_IIDX_WSUB_WIDTH = result.first;
GRAPHICS_IIDX_WSUB_HEIGHT = result.second;
} else {
log_warning("graphics-windowed", "failed to parse -wsubsize");
}
}
if (GRAPHICS_IIDX_WSUB_POS.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_POS");
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(GRAPHICS_IIDX_WSUB_POS.value(), result)) {
GRAPHICS_IIDX_WSUB_X = result.first;
GRAPHICS_IIDX_WSUB_Y = result.second;
} else {
log_warning("graphics-windowed", "failed to parse -wsubpos");
}
}
}
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (!GRAPHICS_WINDOWED) {
return;
+59
View File
@@ -0,0 +1,59 @@
#include "external/nvapi/nvapi.h"
#include "external/nvapi/NvApiDriverSettings.h"
#include "hooks/libraryhook.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "nvapi_hook.h"
namespace nvapi_hook {
bool BYPASS_NVAPI = false;
typedef uintptr_t *(*NvAPI_QueryInterface_t)(unsigned int);
static NvAPI_QueryInterface_t NvAPI_QueryInterface_orig = nullptr;
static uintptr_t* __cdecl NvAPI_QueryInterface_hook(unsigned int func_code);
static NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook(
NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags);
void initialize(HINSTANCE dll) {
#ifdef SPICE64
std::string nvapi_dll = "nvapi64.dll";
#else
std::string nvapi_dll = "nvapi.dll";
#endif
detour::trampoline_try(
nvapi_dll.c_str(), "nvapi_QueryInterface",
NvAPI_QueryInterface_hook, &NvAPI_QueryInterface_orig);
}
uintptr_t* __cdecl NvAPI_QueryInterface_hook(unsigned int func_code) {
if (BYPASS_NVAPI) {
log_misc(
"nvapi_hook",
"NvAPI_QueryInterface(0x{:x}) - block all calls to nvapi (-nonvapi)",
func_code);
return nullptr;
}
// NvAPI_DISP_SetDisplayConfig
if (func_code == 0x5D8CF8DE) {
log_misc("nvapi_hook", "NvAPI_QueryInterface(NvAPI_DISP_SetDisplayConfig) - hooked");
return (uintptr_t *)NvAPI_DISP_SetDisplayConfig_hook;
}
// all others: let the game call nvapi directly
log_misc("nvapi_hook", "NvAPI_QueryInterface(0x{:x}) - pass through to nvapi", func_code);
return NvAPI_QueryInterface_orig(func_code);
}
NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook(
NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags) {
log_misc("nvapi_hook", "NvAPI_DISP_SetDisplayConfig_hook - do nothing and return");
return NVAPI_OK;
}
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
namespace nvapi_hook {
extern bool BYPASS_NVAPI;
void initialize(HINSTANCE dll);
}
+175
View File
@@ -0,0 +1,175 @@
#include <d3d9.h>
#include <optional>
#include "avs/game.h"
#include "external/nvenc/nvEncodeAPI.h"
#include "hooks/libraryhook.h"
#include "hooks/graphics/backends/d3d9/d3d9_device.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "nvenc_hook.h"
#ifdef SPICE64
typedef NVENCSTATUS(NVENCAPI *NvEncodeAPICreateInstance_Type)(NV_ENCODE_API_FUNCTION_LIST*);
static NvEncodeAPICreateInstance_Type NvEncodeAPICreateInstance_orig = nullptr;
static PNVENCOPENENCODESESSIONEX nvEncOpenEncodeSessionEx_orig = nullptr;
static PNVENCINITIALIZEENCODER nvEncInitializeEncoder_orig = nullptr;
static BOOL initialized = false;
namespace nvenc_hook {
std::optional<std::string> VIDEO_CQP_STRING_OVERRIDE;
std::optional<NV_ENC_QP> VIDEO_CQP_OVERRIDE;
void parse_qcp_params();
NVENCSTATUS NVENCAPI nvEncOpenEncodeSessionEx_hook(
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS *openSessionExParams,
void **encoder
) {
WrappedIDirect3DDevice9 *wrappedDevice;
try {
wrappedDevice = (WrappedIDirect3DDevice9*)openSessionExParams->device;
// log_misc("nvenc_hook",
// "nvEncOpenEncodeSessionEx hook hit (wrapped: {}) (real: {})",
// fmt::ptr(wrappedDevice),
// fmt::ptr(wrappedDevice->pReal)
// );
openSessionExParams->device = wrappedDevice->pReal;
} catch (const std::exception &ex) {}
return nvEncOpenEncodeSessionEx_orig(openSessionExParams, encoder);
}
NVENCSTATUS NVENCAPI nvEncInitializeEncoder_hook(
void* encoder,
NV_ENC_INITIALIZE_PARAMS* createEncodeParams
) {
try {
// check input params
if (createEncodeParams == nullptr || createEncodeParams->encodeConfig == nullptr) {
goto done;
}
const auto rc = &createEncodeParams->encodeConfig->rcParams;
if (rc->rateControlMode != NV_ENC_PARAMS_RC_CONSTQP) {
log_warning(
"nvenc_hook",
"nvEncInitializeEncoder: unexpected rateControlMode, expected: NV_ENC_PARAMS_RC_CONSTQP, actual: {}",
rc->rateControlMode);
goto done;
}
log_misc("nvenc_hook", "nvEncInitializeEncoder hook hit with expected params");
// print out most relevant video quality settings
// note: NvEncoder.cpp sample uses {28, 31, 25} (and that's what some hex edits modify)
// but bm2dx later adds 8 to each value, before calling this routine
log_misc(
"nvenc_hook", "nvEncInitializeEncoder: original constQP p={}, b={}, i={}",
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra);
// video quality override
if (VIDEO_CQP_OVERRIDE.has_value()) {
rc->constQP = VIDEO_CQP_OVERRIDE.value();
log_misc(
"nvenc_hook", "nvEncInitializeEncoder: user overriden constQP p={}, b={}, i={}",
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra);
}
} catch (const std::exception &ex) {}
done:
return nvEncInitializeEncoder_orig(encoder, createEncodeParams);
}
NVENCSTATUS NVENCAPI NvEncodeAPICreateInstance_hook(NV_ENCODE_API_FUNCTION_LIST *pFunctionList) {
// log_misc("nvenc_hook", "NvEncodeAPICreateInstance hook hit");
auto status = NvEncodeAPICreateInstance_orig(pFunctionList);
// The game will call NvEncodeAPICreateInstance multiple times
// Using a flag to avoid creating trampoline repeatedly
if (!initialized) {
// hook functions
detour::trampoline_try(
pFunctionList->nvEncOpenEncodeSessionEx,
nvEncOpenEncodeSessionEx_hook,
&nvEncOpenEncodeSessionEx_orig);
detour::trampoline_try(
pFunctionList->nvEncInitializeEncoder,
nvEncInitializeEncoder_hook,
&nvEncInitializeEncoder_orig);
log_misc("nvenc_hook", "NvEncodeAPICreateInstance_hook called and functions hooked");
initialized = true;
}
return status;
}
void initialize() {
HMODULE nvenc = libutils::try_library("nvEncodeAPI64.dll");
if (nvenc == nullptr) {
return;
}
bool success = detour::trampoline_try(
(NvEncodeAPICreateInstance_Type)libutils::try_proc(nvenc, "NvEncodeAPICreateInstance"),
NvEncodeAPICreateInstance_hook,
&NvEncodeAPICreateInstance_orig
);
if (success) {
log_misc("nvenc_hook", "created hook for NvEncodeAPICreateInstance");
} else {
log_warning("nvenc_hook", "failed to hook NvEncodeAPICreateInstance");
}
parse_qcp_params();
}
void parse_qcp_params() {
if (!VIDEO_CQP_STRING_OVERRIDE.has_value() || VIDEO_CQP_STRING_OVERRIDE.value().empty()) {
return;
}
const auto remove_spaces = [](const char& c) {
return c == ' ';
};
auto s = VIDEO_CQP_STRING_OVERRIDE.value();
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
int cqp = -1;
NV_ENC_QP nvenc_qp;
// try 3-param format first, and then fall back to single parameter format
if (sscanf(
s.c_str(),
"%i,%i,%i",
(int*)&(nvenc_qp.qpInterP),
(int*)&(nvenc_qp.qpInterB),
(int*)&(nvenc_qp.qpIntra)) == 3) {
VIDEO_CQP_OVERRIDE = nvenc_qp;
} else if (sscanf(s.c_str(), "%i", &cqp) == 1) {
nvenc_qp.qpInterP = cqp;
nvenc_qp.qpInterB = cqp;
nvenc_qp.qpIntra = cqp;
VIDEO_CQP_OVERRIDE = nvenc_qp;
} else {
log_fatal("nvenc_hook", "failed to parse -iidxreccqp");
}
log_info("nvenc_hook", "parsed NVENC ConstQP params: p={}, b={}, i={} (-iidxreccqp)",
nvenc_qp.qpInterP, nvenc_qp.qpInterB, nvenc_qp.qpIntra);
}
}
#else
namespace nvenc_hook {
void initialize() {
return;
}
}
#endif
+7
View File
@@ -0,0 +1,7 @@
#pragma once
namespace nvenc_hook {
extern std::optional<std::string> VIDEO_CQP_STRING_OVERRIDE;
void initialize();
}