mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-09-27 09:23:21 +03:00
## Link to GitHub Issue or related Pull Request, if one exists Part 1 of many fixes for #814. ## Description of change For the native touch hook - add code to inject synthetic touches using Windows API (`InjectTouchInput`). Note that this is Windows 8+ only, but we can make an assumption that we are running on Win10+ for these games (TDJ/UFC/High Cheers) since the cabs assume Win10. Detect mouse events and allow IIDX poke to call into this to inject synthetic touches. Also, update the nativetouchhook to independently calculate window size and rotation, instead of relying on rawinput layer. ## Testing Tested to work with native touch option on IIDX, SDVX, POPN, all full screen / windowed / sub on/off combinations. Edge cases: * sdvx main monitor rotated 270 deg * touch invert option * windowed mode resize / moved All seem to work.
1758 lines
57 KiB
C++
1758 lines
57 KiB
C++
// GetDisplayConfigBufferSizes etc is Vista+
|
|
#define _WIN32_WINNT 0x0601
|
|
|
|
#include <initguid.h>
|
|
|
|
#include "graphics.h"
|
|
|
|
#include <chrono>
|
|
#include <set>
|
|
#include <vector>
|
|
#include <mutex>
|
|
#include <condition_variable>
|
|
|
|
#include "avs/game.h"
|
|
#include "cfg/icon.h"
|
|
#include "cfg/screen_resize.h"
|
|
#include "games/ddr/ddr.h"
|
|
#include "games/gitadora/gitadora.h"
|
|
#include "games/iidx/iidx.h"
|
|
#include "games/popn/popn.h"
|
|
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
|
|
#include "hooks/graphics/backends/d3d11/d3d11_backend.h"
|
|
#include "launcher/shutdown.h"
|
|
#include "overlay/overlay.h"
|
|
#include "touch/touch.h"
|
|
#include "touch/touch_gestures.h"
|
|
#include "util/detour.h"
|
|
#include "util/logging.h"
|
|
#include "util/fileutils.h"
|
|
#include "util/utils.h"
|
|
#include "misc/wintouchemu.h"
|
|
#include "touch/native/inject.h"
|
|
#include "util/time.h"
|
|
#include "rawinput/rawinput.h"
|
|
|
|
struct CaptureData {
|
|
std::shared_ptr<uint8_t[]> data;
|
|
unsigned short width, height;
|
|
uint64_t timestamp;
|
|
};
|
|
|
|
HWND TDJ_SUBSCREEN_WINDOW = nullptr;
|
|
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
|
|
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
|
|
static HWND GFDM_LEFT_WINDOW = nullptr;
|
|
static HWND GFDM_RIGHT_WINDOW = nullptr;
|
|
HWND POPN_SUBSCREEN_WINDOW = nullptr;
|
|
bool FAKE_SUBSCREEN_ADAPTER = false;
|
|
|
|
// 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 };
|
|
static std::mutex GRAPHICS_SCREENS_M {};
|
|
static std::vector<int> GRAPHICS_CAPTURE_SCREENS;
|
|
static const size_t GRAPHICS_CAPTURE_SCREEN_NO = 4;
|
|
static std::mutex GRAPHICS_CAPTURE_SCREENS_M {};
|
|
static CaptureData GRAPHICS_CAPTURE_BUFFER[GRAPHICS_CAPTURE_SCREEN_NO] {};
|
|
static std::mutex GRAPHICS_CAPTURE_BUFFER_M[GRAPHICS_CAPTURE_SCREEN_NO] {};
|
|
static std::condition_variable GRAPHICS_CAPTURE_CV[GRAPHICS_CAPTURE_SCREEN_NO] {};
|
|
static bool GRAPHICS_CAPTURE_SKIP_SIGNAL[GRAPHICS_CAPTURE_SCREEN_NO] {};
|
|
static constexpr std::chrono::milliseconds GRAPHICS_CAPTURE_RECEIVE_TIMEOUT {2000};
|
|
|
|
static void graphics_capture_cancel_pending(int screen) {
|
|
std::lock_guard<std::mutex> lock(GRAPHICS_CAPTURE_SCREENS_M);
|
|
|
|
for (auto it = GRAPHICS_CAPTURE_SCREENS.rbegin(); it != GRAPHICS_CAPTURE_SCREENS.rend(); ++it) {
|
|
if (*it == screen) {
|
|
GRAPHICS_CAPTURE_SCREENS.erase(std::next(it).base());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static std::optional<graphics_orientation> target_orientation_on_boot;
|
|
static UINT target_refresh_rate_on_boot = 0;
|
|
static std::optional<std::pair<uint32_t, uint32_t>> target_resolution_on_boot;
|
|
static bool monitor_settings_changed = false;
|
|
static bool monitor_layout_needs_reset = false;
|
|
|
|
// flag settings
|
|
bool GRAPHICS_CAPTURE_CURSOR = false;
|
|
bool GRAPHICS_LOG_HRESULT = false;
|
|
bool GRAPHICS_SDVX_FORCE_720 = false;
|
|
#ifdef SPICE64
|
|
SdvxLive2dMode GRAPHICS_SDVX_LIVE2D_MODE = SdvxLive2dMode::Off;
|
|
std::atomic<bool> GRAPHICS_SDVX_LIVE2D_IN_GAMEPLAY = false;
|
|
#endif // SPICE64
|
|
bool GRAPHICS_SHOW_CURSOR = false;
|
|
bool GRAPHICS_WINDOWED = false;
|
|
std::vector<HWND> GRAPHICS_WINDOWS;
|
|
UINT GRAPHICS_FORCE_REFRESH = 0;
|
|
std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
|
|
std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
|
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
|
|
bool GRAPHICS_PREVENT_SECONDARY_WINDOWS = false;
|
|
bool GRAPHICS_GITADORA_HIDE_SIDE_WINDOWS = 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;
|
|
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION_SUB;
|
|
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";
|
|
std::string GRAPHICS_SCREENSHOT_DIR = ".\\screenshots";
|
|
|
|
static decltype(ChangeDisplaySettingsA) *ChangeDisplaySettingsA_orig = nullptr;
|
|
static decltype(ChangeDisplaySettingsExA) *ChangeDisplaySettingsExA_orig = nullptr;
|
|
static decltype(ClipCursor) *ClipCursor_orig = nullptr;
|
|
static decltype(CreateWindowExA) *CreateWindowExA_orig = nullptr;
|
|
static decltype(CreateWindowExW) *CreateWindowExW_orig = nullptr;
|
|
static decltype(EnableWindow) *EnableWindow_orig = nullptr;
|
|
static decltype(EnumDisplayDevicesA) *EnumDisplayDevicesA_orig = nullptr;
|
|
static decltype(MoveWindow) *MoveWindow_orig = nullptr;
|
|
static decltype(PeekMessageA) *PeekMessageA_orig = nullptr;
|
|
static decltype(RegisterClassA) *RegisterClassA_orig = nullptr;
|
|
static decltype(RegisterClassExA) *RegisterClassExA_orig = nullptr;
|
|
static decltype(RegisterClassW) *RegisterClassW_orig = nullptr;
|
|
static decltype(RegisterClassExW) *RegisterClassExW_orig = nullptr;
|
|
static decltype(ShowCursor) *ShowCursor_orig = nullptr;
|
|
static decltype(SetCursor) *SetCursor_orig = nullptr;
|
|
static decltype(SetWindowLongA) *SetWindowLongA_orig = nullptr;
|
|
static decltype(SetWindowLongW) *SetWindowLongW_orig = nullptr;
|
|
static decltype(SetWindowPos) *SetWindowPos_orig = nullptr;
|
|
static decltype(ShowWindow) *ShowWindow_orig = nullptr;
|
|
static decltype(SetDisplayConfig) *SetDisplayConfig_addr = nullptr;
|
|
|
|
static void reset_window_hook(HWND hWnd) {
|
|
overlay::destroy(hWnd);
|
|
|
|
if (WNDPROC_ORIG) {
|
|
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, (LONG_PTR) WNDPROC_ORIG);
|
|
WNDPROC_ORIG = nullptr;
|
|
}
|
|
}
|
|
|
|
static std::string gitadora_canonical_window_name(const std::string &window_name) {
|
|
if (window_name == "GITADORA") {
|
|
return "GITADORA";
|
|
}
|
|
if (window_name.ends_with("LEFT")) {
|
|
return "LEFT";
|
|
}
|
|
if (window_name.ends_with("RIGHT")) {
|
|
return "RIGHT";
|
|
}
|
|
if (window_name.ends_with("SMALL")) {
|
|
return "SMALL";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
static const char *gitadora_window_name_for_hwnd(HWND hWnd) {
|
|
if (hWnd == nullptr) {
|
|
return nullptr;
|
|
}
|
|
if (GRAPHICS_HOOKED_WINDOW.has_value() && hWnd == GRAPHICS_HOOKED_WINDOW.value()) {
|
|
return "GITADORA";
|
|
}
|
|
if (hWnd == GFDM_LEFT_WINDOW) {
|
|
return "LEFT";
|
|
}
|
|
if (hWnd == GFDM_RIGHT_WINDOW) {
|
|
return "RIGHT";
|
|
}
|
|
if (hWnd == GFDM_SUBSCREEN_WINDOW) {
|
|
return "SMALL";
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static bool is_gfdm_known_window(HWND hWnd) {
|
|
return gitadora_window_name_for_hwnd(hWnd) != nullptr;
|
|
}
|
|
|
|
static bool gitadora_should_block_game_window_placement(HWND hWnd) {
|
|
if (!GRAPHICS_WINDOWED || !games::gitadora::is_arena_model()) {
|
|
return false;
|
|
}
|
|
|
|
const auto window_name = gitadora_window_name_for_hwnd(hWnd);
|
|
return window_name != nullptr && graphics_gitadora_has_window_monitor(window_name);
|
|
}
|
|
|
|
static void gitadora_remember_window(HWND hWnd, const std::string &window_name) {
|
|
if (window_name == "LEFT") {
|
|
GFDM_LEFT_WINDOW = hWnd;
|
|
} else if (window_name == "RIGHT") {
|
|
GFDM_RIGHT_WINDOW = hWnd;
|
|
} else if (window_name == "SMALL") {
|
|
GFDM_SUBSCREEN_WINDOW = hWnd;
|
|
}
|
|
}
|
|
|
|
static bool gitadora_should_allow_small_resize() {
|
|
return GRAPHICS_WINDOWED &&
|
|
games::gitadora::is_arena_model() &&
|
|
!graphics_gitadora_is_borderless_windowed();
|
|
}
|
|
|
|
static void gitadora_apply_small_resize_style(DWORD &style) {
|
|
if (!gitadora_should_allow_small_resize()) {
|
|
return;
|
|
}
|
|
|
|
style |= WS_SIZEBOX;
|
|
style |= WS_MAXIMIZEBOX;
|
|
style |= WS_SYSMENU;
|
|
}
|
|
|
|
static void gitadora_force_window_style(HWND hWnd) {
|
|
if (!GRAPHICS_WINDOWED || !games::gitadora::is_arena_model() || hWnd == nullptr) {
|
|
return;
|
|
}
|
|
|
|
DWORD style = GetWindowLongA(hWnd, GWL_STYLE);
|
|
DWORD style_ex = GetWindowLongA(hWnd, GWL_EXSTYLE);
|
|
const DWORD style_orig = style;
|
|
const DWORD style_ex_orig = style_ex;
|
|
graphics_gitadora_apply_window_style(style, style_ex);
|
|
if (hWnd == GFDM_SUBSCREEN_WINDOW) {
|
|
gitadora_apply_small_resize_style(style);
|
|
}
|
|
|
|
if (style == style_orig && style_ex == style_ex_orig) {
|
|
return;
|
|
}
|
|
|
|
if (SetWindowLongA_orig != nullptr) {
|
|
SetWindowLongA_orig(hWnd, GWL_STYLE, static_cast<LONG>(style));
|
|
SetWindowLongA_orig(hWnd, GWL_EXSTYLE, static_cast<LONG>(style_ex));
|
|
} else {
|
|
SetWindowLongA(hWnd, GWL_STYLE, static_cast<LONG>(style));
|
|
SetWindowLongA(hWnd, GWL_EXSTYLE, static_cast<LONG>(style_ex));
|
|
}
|
|
|
|
const UINT flags =
|
|
SWP_NOMOVE |
|
|
SWP_NOSIZE |
|
|
SWP_NOZORDER |
|
|
SWP_NOACTIVATE |
|
|
SWP_FRAMECHANGED;
|
|
if (SetWindowPos_orig != nullptr) {
|
|
SetWindowPos_orig(hWnd, nullptr, 0, 0, 0, 0, flags);
|
|
} else {
|
|
SetWindowPos(hWnd, nullptr, 0, 0, 0, 0, flags);
|
|
}
|
|
|
|
log_misc(
|
|
"graphics",
|
|
"GITADORA window style override: hwnd={}, style 0x{:x}->0x{:x}, ex 0x{:x}->0x{:x}",
|
|
fmt::ptr(hWnd),
|
|
style_orig,
|
|
style,
|
|
style_ex_orig,
|
|
style_ex);
|
|
}
|
|
|
|
// window procedure
|
|
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
|
|
// terminate
|
|
if (uMsg == WM_CLOSE) {
|
|
log_info("graphics", "detected WM_CLOSE, terminating...");
|
|
launcher::shutdown(0);
|
|
return false;
|
|
}
|
|
|
|
// overlay specific
|
|
if (overlay::OVERLAY) {
|
|
switch (uMsg) {
|
|
case WM_CHAR: {
|
|
|
|
// input characters if overlay is active
|
|
if (overlay::OVERLAY->has_focus()) {
|
|
overlay::OVERLAY->input_char((unsigned int) wParam);
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case WM_DESTROY: {
|
|
auto wndproc = WNDPROC_ORIG;
|
|
reset_window_hook(hWnd);
|
|
return CallWindowProcA(wndproc, hWnd, uMsg, wParam, lParam);
|
|
}
|
|
case WM_SETCURSOR: {
|
|
|
|
// set cursor back to the overlay one
|
|
if (LOWORD(lParam) == HTCLIENT && overlay::OVERLAY->update_cursor()) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (wintouchemu::INJECT_MOUSE_AS_WM_TOUCH) {
|
|
// drop mouse inputs since only wintouches should be used
|
|
switch (uMsg) {
|
|
case WM_MOUSEMOVE:
|
|
case WM_LBUTTONDOWN:
|
|
case WM_LBUTTONUP:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_MBUTTONUP:
|
|
case WM_RBUTTONDOWN:
|
|
case WM_RBUTTONUP:
|
|
case WM_XBUTTONDOWN:
|
|
case WM_XBUTTONUP:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// window resize
|
|
graphics_windowed_wndproc(hWnd, uMsg, wParam, lParam);
|
|
|
|
// call custom procedures
|
|
for (WNDPROC wndProc : WNDPROC_CUSTOM) {
|
|
wndProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
// capture mouse
|
|
if (GRAPHICS_CAPTURE_CURSOR) {
|
|
bool free_cursor = false;
|
|
bool capture_cursor = false;
|
|
bool early_return = false;
|
|
switch (uMsg) {
|
|
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
|
|
switch (uMsg) {
|
|
case WM_SYSKEYDOWN:
|
|
case WM_SYSKEYUP:
|
|
case WM_KEYDOWN:
|
|
case WM_KEYUP:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
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);
|
|
}
|
|
break;
|
|
case WM_ACTIVATEAPP:
|
|
if (wParam) {
|
|
// regained focus
|
|
// this *can* get called twice in a row when restoring, but update_monitor_at_runtime
|
|
// is idempotent (checks current display settings to see if changes are needed),
|
|
// so it shouldn't cause any issues
|
|
update_monitor_at_runtime();
|
|
}
|
|
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;
|
|
}
|
|
|
|
if (hWnd == GFDM_SUBSCREEN_WINDOW && (uMsg == WM_MOVE || uMsg == WM_SIZE)) {
|
|
update_spicetouch_window_dimensions(hWnd);
|
|
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);
|
|
}
|
|
}
|
|
|
|
return CallWindowProcA(WSUB_WNDPROC_ORIG, hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
static LONG WINAPI ChangeDisplaySettingsA_hook(DEVMODEA *lpDevMode, DWORD dwflags) {
|
|
log_misc("graphics", "ChangeDisplaySettingsA hook hit");
|
|
|
|
// ignore display settings changes when running windowed
|
|
if (GRAPHICS_WINDOWED) {
|
|
return DISP_CHANGE_SUCCESSFUL;
|
|
}
|
|
|
|
// call original
|
|
return ChangeDisplaySettingsA_orig(lpDevMode, dwflags);
|
|
}
|
|
|
|
static LONG WINAPI ChangeDisplaySettingsExA_hook(LPCSTR lpszDeviceName, DEVMODEA *lpDevMode, HWND hwnd,
|
|
DWORD dwflags, LPVOID lParam)
|
|
{
|
|
log_misc("graphics", "ChangeDisplaySettingsExA hook hit");
|
|
|
|
// ignore display settings changes when running windowed
|
|
if (GRAPHICS_WINDOWED) {
|
|
return DISP_CHANGE_SUCCESSFUL;
|
|
}
|
|
|
|
// call original
|
|
return ChangeDisplaySettingsExA_orig(lpszDeviceName, lpDevMode, hwnd, dwflags, lParam);
|
|
}
|
|
|
|
static BOOL WINAPI ClipCursor_hook(const RECT *lpRect) {
|
|
log_misc("graphics", "ClipCursor hook hit");
|
|
|
|
// ignore cursor confine when having no explicit cursor confine
|
|
if (!GRAPHICS_CAPTURE_CURSOR) {
|
|
return TRUE;
|
|
}
|
|
|
|
// call original
|
|
return ClipCursor_orig(lpRect);
|
|
}
|
|
|
|
static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName,
|
|
DWORD dwStyle, int x, int y, int nWidth, int nHeight,
|
|
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),
|
|
window_name,
|
|
dwStyle,
|
|
x,
|
|
y,
|
|
nWidth,
|
|
nHeight,
|
|
fmt::ptr(hWndParent),
|
|
fmt::ptr(hMenu),
|
|
fmt::ptr(hInstance),
|
|
fmt::ptr(lpParam));
|
|
|
|
// gfdm
|
|
std::string effective_window_name = window_name;
|
|
if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
|
|
// set window name
|
|
if (!lpWindowName) {
|
|
lpWindowName = "GITADORA";
|
|
effective_window_name = "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");
|
|
bool is_popn_sub_window = avs::game::is_model("M39") && window_name.ends_with("Sub Screen");
|
|
const std::string gfdm_window_name = games::gitadora::is_arena_model()
|
|
? gitadora_canonical_window_name(effective_window_name)
|
|
: "";
|
|
const bool is_gfdm_window = !gfdm_window_name.empty();
|
|
const bool is_gfdm_sub_window = gfdm_window_name == "SMALL";
|
|
const bool allow_gfdm_small_resize =
|
|
is_gfdm_sub_window && gitadora_should_allow_small_resize();
|
|
|
|
// update style / ex-style
|
|
if (is_tdj_sub_window || is_sdvx_sub_window || is_gfdm_sub_window || is_popn_sub_window) {
|
|
// hide maximize button (prevent misaligned touches)
|
|
if (!allow_gfdm_small_resize) {
|
|
dwStyle &= ~(WS_MAXIMIZEBOX);
|
|
}
|
|
|
|
// mouse clicks become misaligned when resized
|
|
if (!allow_gfdm_small_resize) {
|
|
dwStyle &= ~(WS_SIZEBOX);
|
|
}
|
|
|
|
// borderless
|
|
if (GRAPHICS_WINDOWED && GRAPHICS_WSUB_BORDERLESS) {
|
|
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
|
|
}
|
|
|
|
// don't show the sub window on task bar / alt-tab targets
|
|
if (!GRAPHICS_WINDOWED) {
|
|
dwExStyle &= ~(WS_EX_APPWINDOW);
|
|
dwExStyle |= WS_EX_TOOLWINDOW;
|
|
}
|
|
}
|
|
|
|
if (allow_gfdm_small_resize) {
|
|
gitadora_apply_small_resize_style(dwStyle);
|
|
}
|
|
if (is_gfdm_window) {
|
|
graphics_gitadora_apply_window_style(dwStyle, dwExStyle);
|
|
}
|
|
|
|
if (is_sdvx_sub_window) {
|
|
graphics_load_windowed_subscreen_parameters();
|
|
if (GRAPHICS_WSUB_SIZE.has_value()) {
|
|
nWidth = GRAPHICS_WSUB_WIDTH;
|
|
nHeight = GRAPHICS_WSUB_HEIGHT;
|
|
} else {
|
|
GRAPHICS_WSUB_WIDTH = nWidth;
|
|
GRAPHICS_WSUB_HEIGHT = nHeight;
|
|
}
|
|
if (GRAPHICS_WSUB_POS.has_value()) {
|
|
x = GRAPHICS_WSUB_X;
|
|
y = GRAPHICS_WSUB_Y;
|
|
} else {
|
|
GRAPHICS_WSUB_X = x;
|
|
GRAPHICS_WSUB_Y = y;
|
|
}
|
|
}
|
|
|
|
if (is_gfdm_window) {
|
|
graphics_gitadora_apply_window_monitor(
|
|
gfdm_window_name,
|
|
x,
|
|
y,
|
|
nWidth,
|
|
nHeight,
|
|
true);
|
|
}
|
|
|
|
if (GRAPHICS_WINDOWED) {
|
|
graphics_window_check_bounds_before_creation(x, y, nWidth, nHeight);
|
|
}
|
|
|
|
// call original
|
|
HWND result = CreateWindowExA_orig(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
|
|
hWndParent, hMenu, hInstance, lpParam);
|
|
GRAPHICS_WINDOWS.push_back(result);
|
|
|
|
// theme the native title bar (dark/light)
|
|
set_window_dark_titlebar(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);
|
|
}
|
|
|
|
// only hook touch window if multiple windows are allowed
|
|
if (gfdm_window_name == "LEFT" || gfdm_window_name == "RIGHT") {
|
|
gitadora_remember_window(result, gfdm_window_name);
|
|
}
|
|
if (is_gfdm_sub_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
|
gitadora_remember_window(result, gfdm_window_name);
|
|
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
|
}
|
|
if (is_gfdm_window && GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
|
gitadora_force_window_style(result);
|
|
}
|
|
|
|
if (is_popn_sub_window) {
|
|
POPN_SUBSCREEN_WINDOW = result;
|
|
if (!GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
|
graphics_hook_subscreen_window(POPN_SUBSCREEN_WINDOW);
|
|
}
|
|
}
|
|
|
|
disable_touch_gestures(result);
|
|
log_misc(
|
|
"graphics",
|
|
"CreateWindowExA returned {}, {}",
|
|
fmt::ptr(result),
|
|
lpWindowName ? lpWindowName : "(null)");
|
|
|
|
return result;
|
|
}
|
|
|
|
static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName,
|
|
DWORD dwStyle, int x, int y, int nWidth, int nHeight,
|
|
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
|
|
LPVOID lpParam)
|
|
{
|
|
log_misc("graphics", "CreateWindowExW hook hit ({:x}, {}, {}, {:x}, {}, {}, {}, {}, {}, {}, {}, {})",
|
|
dwExStyle,
|
|
fmt::ptr(lpClassName),
|
|
lpWindowName != nullptr ? ws2s(lpWindowName) : "(null)",
|
|
dwStyle,
|
|
x,
|
|
y,
|
|
nWidth,
|
|
nHeight,
|
|
fmt::ptr(hWndParent),
|
|
fmt::ptr(hMenu),
|
|
fmt::ptr(hInstance),
|
|
fmt::ptr(lpParam));
|
|
|
|
// DDR specific stuff
|
|
if (avs::game::is_model("MDX")) {
|
|
|
|
// set window name
|
|
if (!lpWindowName) {
|
|
lpWindowName = L"Dance Dance Revolution";
|
|
}
|
|
|
|
// windowed mode adjustments
|
|
// check the width to filter out invisible system-created windows
|
|
if (GRAPHICS_WINDOWED && nWidth > 0) {
|
|
|
|
// change window style
|
|
dwExStyle = 0;
|
|
dwStyle |= WS_OVERLAPPEDWINDOW;
|
|
|
|
// adjust window size to include window decoration
|
|
RECT rect {};
|
|
if (games::ddr::SDMODE) {
|
|
SetRect(&rect, 0, 0, 800, 600);
|
|
} else {
|
|
SetRect(&rect, 0, 0, 1280, 720);
|
|
}
|
|
AdjustWindowRect(&rect, dwStyle, (hMenu != nullptr));
|
|
|
|
nWidth = rect.right - rect.left;
|
|
nHeight = rect.bottom - rect.top;
|
|
}
|
|
}
|
|
|
|
// DanEvo specific stuff
|
|
if (avs::game::is_model("KDM")) {
|
|
|
|
// set window name
|
|
if (!lpWindowName) {
|
|
lpWindowName = L"Dance Evolution";
|
|
}
|
|
}
|
|
|
|
// FTT
|
|
if (avs::game::is_model("MMD")) {
|
|
// set window name
|
|
if (!lpWindowName) {
|
|
lpWindowName = L"Future TomTom";
|
|
}
|
|
}
|
|
|
|
if (GRAPHICS_WINDOWED) {
|
|
graphics_window_check_bounds_before_creation(x, y, nWidth, nHeight);
|
|
}
|
|
|
|
// call original
|
|
HWND result = CreateWindowExW_orig(
|
|
dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
|
|
hWndParent, hMenu, hInstance, lpParam);
|
|
GRAPHICS_WINDOWS.push_back(result);
|
|
|
|
// theme the native title bar (dark/light)
|
|
set_window_dark_titlebar(result);
|
|
|
|
log_misc(
|
|
"graphics",
|
|
"CreateWindowExW returned {}, {}",
|
|
fmt::ptr(result),
|
|
lpWindowName ? ws2s(lpWindowName) : "(null)");
|
|
|
|
disable_touch_gestures(result);
|
|
return result;
|
|
}
|
|
|
|
static BOOL WINAPI EnableWindow_hook(HWND hWnd, BOOL bEnable) {
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL WINAPI EnumDisplayDevicesA_hook(LPCTSTR lpDevice, DWORD iDevNum,
|
|
PDISPLAY_DEVICE lpDisplayDevice, DWORD dwFlags) {
|
|
|
|
// call original
|
|
BOOL value = EnumDisplayDevicesA_orig(lpDevice, iDevNum, lpDisplayDevice, dwFlags);
|
|
|
|
#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;
|
|
}
|
|
|
|
static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint) {
|
|
log_misc("graphics", "MoveWindow hook hit ({}, {}, {}, {}, {}, {})",
|
|
fmt::ptr(hWnd),
|
|
X,
|
|
Y,
|
|
nWidth,
|
|
nHeight,
|
|
bRepaint);
|
|
|
|
// sound voltex windowed mode adjustments
|
|
if (GRAPHICS_WINDOWED && GRAPHICS_SDVX_FORCE_720 && avs::game::is_model("KFC")) {
|
|
RECT rect {};
|
|
DWORD dwStyle;
|
|
|
|
dwStyle = GetWindowLongA(hWnd, GWL_STYLE);
|
|
|
|
// luckily, SDVX does not draw a menu. So we can leave the last
|
|
// argument to `AdjustWindowRect` as `0`.
|
|
SetRect(&rect, 0, 0, 720, 1280);
|
|
AdjustWindowRect(&rect, dwStyle, 0);
|
|
|
|
nWidth = rect.right - rect.left;
|
|
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_WSUB_WIDTH, GRAPHICS_WSUB_HEIGHT);
|
|
AdjustWindowRect(&rect, dwStyle, 0);
|
|
|
|
X = GRAPHICS_WSUB_X;
|
|
Y = GRAPHICS_WSUB_Y;
|
|
|
|
nWidth = rect.right - rect.left;
|
|
nHeight = rect.bottom - rect.top;
|
|
|
|
if (games::iidx::NATIVE_TOUCH) {
|
|
nativetouch::inject::register_and_attach_window(TDJ_SUBSCREEN_WINDOW);
|
|
} else {
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Monitor overrides are applied at creation time. Suppress later game
|
|
// placement calls instead of resizing again during scene transitions.
|
|
if (gitadora_should_block_game_window_placement(hWnd)) {
|
|
return TRUE;
|
|
}
|
|
|
|
// call original
|
|
return MoveWindow_orig(hWnd, X, Y, nWidth, nHeight, bRepaint);
|
|
}
|
|
|
|
static BOOL WINAPI PeekMessageA_hook(
|
|
LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) {
|
|
|
|
// SDVX polls for messages too slowly
|
|
if (avs::game::is_model("KFC")) {
|
|
|
|
// process remaining messages
|
|
BOOL ret;
|
|
while ((ret = PeekMessageA_orig(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, PM_REMOVE)) != 0) {
|
|
if (ret == -1) {
|
|
return ret;
|
|
} else {
|
|
TranslateMessage(lpMsg);
|
|
DispatchMessageA(lpMsg);
|
|
}
|
|
}
|
|
|
|
// return no message
|
|
return FALSE;
|
|
}
|
|
|
|
return PeekMessageA_orig(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
|
|
}
|
|
|
|
static int WINAPI ShowCursor_hook(BOOL bShow) {
|
|
|
|
// prevent game from hiding cursor when option is enabled
|
|
if (GRAPHICS_SHOW_CURSOR && !bShow) {
|
|
return 1;
|
|
}
|
|
|
|
// call original
|
|
return ShowCursor_orig(bShow);
|
|
}
|
|
|
|
static HCURSOR WINAPI SetCursor_hook(HCURSOR hCursor) {
|
|
if (GRAPHICS_SHOW_CURSOR && hCursor == NULL) {
|
|
return GetCursor();
|
|
}
|
|
return SetCursor_orig(hCursor);
|
|
}
|
|
|
|
static LONG WINAPI SetWindowLongA_hook(HWND hWnd, int nIndex, LONG dwNewLong) {
|
|
|
|
// DDR window style fix
|
|
if (nIndex == GWL_STYLE && avs::game::is_model("MDX")) {
|
|
dwNewLong |= WS_OVERLAPPEDWINDOW;
|
|
}
|
|
|
|
const bool force_gfdm_style =
|
|
GRAPHICS_WINDOWED &&
|
|
games::gitadora::is_arena_model() &&
|
|
is_gfdm_known_window(hWnd) &&
|
|
(nIndex == GWL_STYLE || nIndex == GWL_EXSTYLE);
|
|
|
|
const auto result = SetWindowLongA_orig(hWnd, nIndex, dwNewLong);
|
|
if (force_gfdm_style) {
|
|
gitadora_force_window_style(hWnd);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static LONG WINAPI SetWindowLongW_hook(HWND hWnd, int nIndex, LONG dwNewLong) {
|
|
|
|
// DDR overlapped window fix
|
|
if (nIndex == GWL_STYLE && avs::game::is_model("MDX")) {
|
|
dwNewLong |= WS_OVERLAPPEDWINDOW;
|
|
}
|
|
|
|
const bool force_gfdm_style =
|
|
GRAPHICS_WINDOWED &&
|
|
games::gitadora::is_arena_model() &&
|
|
is_gfdm_known_window(hWnd) &&
|
|
(nIndex == GWL_STYLE || nIndex == GWL_EXSTYLE);
|
|
|
|
const auto result = SetWindowLongW_orig(hWnd, nIndex, dwNewLong);
|
|
if (force_gfdm_style) {
|
|
gitadora_force_window_style(hWnd);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
|
|
int X, int Y, int cx, int cy, UINT uFlags) {
|
|
|
|
// windowed mode adjustments
|
|
if (GRAPHICS_WINDOWED && (avs::game::is_model("LMA") || avs::game::is_model("MDX"))) {
|
|
return TRUE;
|
|
}
|
|
|
|
// Monitor overrides are applied at creation time. Suppress later game
|
|
// placement calls instead of resizing again during scene transitions.
|
|
if (gitadora_should_block_game_window_placement(hWnd) &&
|
|
(uFlags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE)) {
|
|
return TRUE;
|
|
}
|
|
|
|
// prevent gitadora arena model from shifting windows around if the user has preferences
|
|
if (GRAPHICS_WINDOWED && games::gitadora::is_arena_model() &&
|
|
GRAPHICS_HOOKED_WINDOW.has_value() && hWnd == GRAPHICS_HOOKED_WINDOW.value() &&
|
|
cfg::SCREENRESIZE->enable_window_resize) {
|
|
return TRUE;
|
|
}
|
|
|
|
// call original
|
|
return SetWindowPos_orig(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags);
|
|
}
|
|
|
|
static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
|
|
if (games::gitadora::is_arena_model() &&
|
|
GRAPHICS_PREVENT_SECONDARY_WINDOWS &&
|
|
hWnd != GRAPHICS_HOOKED_WINDOW) {
|
|
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
|
|
return true;
|
|
}
|
|
|
|
if (games::gitadora::is_arena_model() &&
|
|
GRAPHICS_GITADORA_HIDE_SIDE_WINDOWS &&
|
|
(hWnd == GFDM_LEFT_WINDOW || hWnd == GFDM_RIGHT_WINDOW)) {
|
|
log_info("graphics", "ShowWindow_hook - hiding GITADORA side window {}", fmt::ptr(hWnd));
|
|
return true;
|
|
}
|
|
|
|
if (games::popn::is_pikapika_model() &&
|
|
GRAPHICS_PREVENT_SECONDARY_WINDOWS &&
|
|
hWnd == POPN_SUBSCREEN_WINDOW) {
|
|
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
|
|
return true;
|
|
}
|
|
|
|
// call original
|
|
return ShowWindow_orig(hWnd, nCmdShow);
|
|
}
|
|
|
|
static ATOM WINAPI RegisterClassA_hook(const WNDCLASSA *lpWndClass) {
|
|
|
|
// check for null
|
|
if (!lpWndClass) {
|
|
return RegisterClassA_orig(lpWndClass);
|
|
}
|
|
|
|
// copy struct and use own icon
|
|
WNDCLASSA wnd = *lpWndClass;
|
|
wnd.hIcon = WINDOW_ICON;
|
|
|
|
// call original
|
|
return RegisterClassA_orig(&wnd);
|
|
}
|
|
|
|
static ATOM WINAPI RegisterClassExA_hook(const WNDCLASSEXA *Arg1) {
|
|
|
|
// check for null
|
|
if (!Arg1) {
|
|
return RegisterClassExA_orig(Arg1);
|
|
}
|
|
|
|
// copy struct and use own icon
|
|
WNDCLASSEXA wnd = *Arg1;
|
|
wnd.hIcon = WINDOW_ICON;
|
|
wnd.hIconSm = WINDOW_ICON;
|
|
|
|
// call original
|
|
return RegisterClassExA_orig(&wnd);
|
|
}
|
|
|
|
static ATOM WINAPI RegisterClassW_hook(const WNDCLASSW *lpWndClass) {
|
|
|
|
// check for null
|
|
if (!lpWndClass) {
|
|
return RegisterClassW_orig(lpWndClass);
|
|
}
|
|
|
|
// copy struct and use own icon
|
|
WNDCLASSW wnd = *lpWndClass;
|
|
wnd.hIcon = WINDOW_ICON;
|
|
|
|
// call original
|
|
return RegisterClassW_orig(&wnd);
|
|
}
|
|
|
|
static ATOM WINAPI RegisterClassExW_hook(const WNDCLASSEXW *Arg1) {
|
|
|
|
// check for null
|
|
if (!Arg1) {
|
|
return RegisterClassExW_orig(Arg1);
|
|
}
|
|
|
|
// copy struct and use own icon
|
|
WNDCLASSEXW wnd = *Arg1;
|
|
wnd.hIcon = WINDOW_ICON;
|
|
wnd.hIconSm = WINDOW_ICON;
|
|
|
|
// call original
|
|
return RegisterClassExW_orig(&wnd);
|
|
}
|
|
|
|
static HHOOK WINAPI SetWindowsHookExA_hook(int, HOOKPROC, HINSTANCE, DWORD) {
|
|
log_misc("graphics", "SetWindowsHookExA hook hit");
|
|
|
|
// we don't do hooks
|
|
return nullptr;
|
|
}
|
|
|
|
static BOOL WINAPI SetCursorPos_hook(int, int) {
|
|
|
|
// prevent games from messing with the cursor position themselves
|
|
return TRUE;
|
|
}
|
|
|
|
static int WINAPI MessageBoxA_hook(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
|
|
|
|
auto text = lpText != nullptr ? lpText : "(null)";
|
|
auto title = lpCaption != nullptr ? lpCaption : "(null)";
|
|
|
|
log_info("graphics", "MessageBoxA: {} - {}", title, text);
|
|
return IDOK;
|
|
}
|
|
|
|
static int WINAPI MessageBoxExA_hook(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType, WORD wLanguageId) {
|
|
|
|
auto text = lpText != nullptr ? lpText : "(null)";
|
|
auto title = lpCaption != nullptr ? lpCaption : "(null)";
|
|
|
|
log_info("graphics", "MessageBoxExA: {} - {}", title, text);
|
|
return IDOK;
|
|
}
|
|
|
|
static int WINAPI MessageBoxW_hook(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) {
|
|
|
|
auto text = lpText != nullptr ? lpText : L"(null)";
|
|
auto title = lpCaption != nullptr ? lpCaption : L"(null)";
|
|
|
|
log_info("graphics", "MessageBoxW: {} - {}", ws2s(title), ws2s(text));
|
|
return IDOK;
|
|
}
|
|
|
|
static int WINAPI MessageBoxExW_hook(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType, WORD wLanguageId) {
|
|
|
|
auto text = lpText != nullptr ? lpText : L"(null)";
|
|
auto title = lpCaption != nullptr ? lpCaption : L"(null)";
|
|
|
|
log_info("graphics", "MessageBoxExW: {} - {}", ws2s(title), ws2s(text));
|
|
return IDOK;
|
|
}
|
|
|
|
void graphics_init() {
|
|
log_info("graphics", "initializing");
|
|
|
|
// init screen resize
|
|
log_info("ScreenResize", "initializing");
|
|
if(cfg::SCREENRESIZE == nullptr){
|
|
cfg::SCREENRESIZE = std::make_unique<cfg::ScreenResize>();
|
|
}
|
|
|
|
// init backends
|
|
graphics_d3d9_init();
|
|
graphics_d3d11_init();
|
|
|
|
// general hooks
|
|
ChangeDisplaySettingsA_orig = detour::iat_try("ChangeDisplaySettingsA", ChangeDisplaySettingsA_hook);
|
|
ChangeDisplaySettingsExA_orig = detour::iat_try("ChangeDisplaySettingsExA", ChangeDisplaySettingsExA_hook);
|
|
ClipCursor_orig = detour::iat_try("ClipCursor", ClipCursor_hook);
|
|
CreateWindowExA_orig = detour::iat_try("CreateWindowExA", CreateWindowExA_hook);
|
|
CreateWindowExW_orig = detour::iat_try("CreateWindowExW", CreateWindowExW_hook);
|
|
EnableWindow_orig = detour::iat_try("EnableWindow", EnableWindow_hook);
|
|
EnumDisplayDevicesA_orig = detour::iat_try("EnumDisplayDevicesA", EnumDisplayDevicesA_hook);
|
|
MoveWindow_orig = detour::iat_try("MoveWindow", MoveWindow_hook);
|
|
PeekMessageA_orig = detour::iat_try("PeekMessageA", PeekMessageA_hook);
|
|
RegisterClassA_orig = detour::iat_try("RegisterClassA", RegisterClassA_hook);
|
|
RegisterClassExA_orig = detour::iat_try("RegisterClassExA", RegisterClassExA_hook);
|
|
RegisterClassW_orig = detour::iat_try("RegisterClassW", RegisterClassW_hook);
|
|
RegisterClassExW_orig = detour::iat_try("RegisterClassExW", RegisterClassExW_hook);
|
|
ShowCursor_orig = detour::iat_try("ShowCursor", ShowCursor_hook);
|
|
SetCursor_orig = detour::iat_try("SetCursor", SetCursor_hook);
|
|
SetWindowLongA_orig = detour::iat_try("SetWindowLongA", SetWindowLongA_hook);
|
|
SetWindowLongW_orig = detour::iat_try("SetWindowLongW", SetWindowLongW_hook);
|
|
SetWindowPos_orig = detour::iat_try("SetWindowPos", SetWindowPos_hook);
|
|
ShowWindow_orig = detour::iat_try("ShowWindow", ShowWindow_hook);
|
|
|
|
detour::iat_try("MessageBoxA", MessageBoxA_hook);
|
|
detour::iat_try("MessageBoxExA", MessageBoxExA_hook);
|
|
detour::iat_try("MessageBoxW", MessageBoxW_hook);
|
|
detour::iat_try("MessageBoxExW", MessageBoxExW_hook);
|
|
|
|
detour::iat_try("SetWindowsHookExA", SetWindowsHookExA_hook);
|
|
detour::iat_try("SetCursorPos", SetCursorPos_hook);
|
|
}
|
|
|
|
void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParameters) {
|
|
|
|
// update window size for a few games
|
|
// TODO: make this work on everything
|
|
if (pPresentationParameters != nullptr && GRAPHICS_WINDOWED
|
|
&& (avs::game::is_model({ "K39", "L39", "M39", "JMP", "LDJ" }))) {
|
|
|
|
// check dimensions
|
|
auto new_width = pPresentationParameters->BackBufferWidth;
|
|
auto new_height = pPresentationParameters->BackBufferHeight;
|
|
if (new_width != 0 && new_height != 0) {
|
|
RECT rect {};
|
|
GetWindowRect(hWnd, &rect);
|
|
|
|
auto width = rect.right - rect.left;
|
|
auto height = rect.bottom - rect.top;
|
|
|
|
log_info("graphics", "resized window: {}x{} -> {}x{}", width, height, new_width, new_height);
|
|
|
|
DWORD dwStyle = GetWindowLongA(hWnd, GWL_STYLE);
|
|
DWORD dwExStyle = GetWindowLongA(hWnd, GWL_EXSTYLE);
|
|
HMENU menu = GetMenu(hWnd);
|
|
SetRect(&rect, 0, 0, new_width, new_height);
|
|
AdjustWindowRectEx(&rect, dwStyle, (menu != nullptr), dwExStyle);
|
|
|
|
// make sure the window does not go off the screen
|
|
if (rect.top < 0) {
|
|
rect.bottom += -rect.top;
|
|
rect.top = 0;
|
|
}
|
|
|
|
width = rect.right - rect.left;
|
|
height = rect.bottom - rect.top;
|
|
|
|
SetWindowPos(hWnd, HWND_TOP, rect.left, rect.top, width, height, 0);
|
|
}
|
|
}
|
|
|
|
// show cursor
|
|
if (GRAPHICS_SHOW_CURSOR) {
|
|
ShowCursor(TRUE);
|
|
}
|
|
|
|
// capture mouse
|
|
if (GRAPHICS_CAPTURE_CURSOR) {
|
|
RECT rect {};
|
|
GetWindowRect(hWnd, &rect);
|
|
ClipCursor(&rect);
|
|
}
|
|
|
|
// hook window procedure
|
|
if (WNDPROC_ORIG == nullptr) {
|
|
WNDPROC_ORIG = reinterpret_cast<WNDPROC>(GetWindowLongPtrA(hWnd, GWLP_WNDPROC));
|
|
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WindowProc));
|
|
|
|
const bool native_touch_overlay =
|
|
(games::iidx::NATIVE_TOUCH && !GRAPHICS_IIDX_WSUB) ||
|
|
(games::popn::NATIVE_TOUCH && GRAPHICS_PREVENT_SECONDARY_WINDOWS);
|
|
if (native_touch_overlay) {
|
|
nativetouch::inject::register_and_attach_window(hWnd);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
void graphics_add_wnd_proc(WNDPROC wnd_proc) {
|
|
WNDPROC_CUSTOM.push_back(wnd_proc);
|
|
}
|
|
|
|
void graphics_remove_wnd_proc(WNDPROC wndProc) {
|
|
for (size_t x = 0; x < WNDPROC_CUSTOM.size(); x++) {
|
|
if (WNDPROC_CUSTOM[x] == wndProc) {
|
|
WNDPROC_CUSTOM.erase(WNDPROC_CUSTOM.begin() + x);
|
|
}
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
if (GRAPHICS_WSUB_ALWAYS_ON_TOP) {
|
|
graphics_update_z_order(hWnd, true);
|
|
}
|
|
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
|
|
graphics_set_corner_preference(hWnd, true);
|
|
}
|
|
}
|
|
|
|
void graphics_screens_register(int screen) {
|
|
std::lock_guard<std::mutex> lock(GRAPHICS_SCREENS_M);
|
|
|
|
GRAPHICS_SCREENS.insert(screen);
|
|
}
|
|
|
|
void graphics_screens_unregister(int screen) {
|
|
std::lock_guard<std::mutex> lock(GRAPHICS_SCREENS_M);
|
|
|
|
GRAPHICS_SCREENS.erase(screen);
|
|
}
|
|
|
|
void graphics_screens_get(std::vector<int> &screens) {
|
|
std::lock_guard<std::mutex> lock(GRAPHICS_SCREENS_M);
|
|
|
|
screens.insert(screens.end(), GRAPHICS_SCREENS.begin(), GRAPHICS_SCREENS.end());
|
|
}
|
|
|
|
void graphics_screenshot_trigger() {
|
|
GRAPHICS_SCREENSHOT_TRIGGER = true;
|
|
}
|
|
|
|
bool graphics_screenshot_consume() {
|
|
auto flag = GRAPHICS_SCREENSHOT_TRIGGER;
|
|
GRAPHICS_SCREENSHOT_TRIGGER = false;
|
|
return flag;
|
|
}
|
|
|
|
void graphics_capture_trigger(int screen) {
|
|
std::lock_guard<std::mutex> lock(GRAPHICS_CAPTURE_SCREENS_M);
|
|
|
|
GRAPHICS_CAPTURE_SCREENS.push_back(screen);
|
|
}
|
|
|
|
bool graphics_capture_consume(int *screen) {
|
|
std::lock_guard<std::mutex> lock(GRAPHICS_CAPTURE_SCREENS_M);
|
|
|
|
if (GRAPHICS_CAPTURE_SCREENS.empty()) {
|
|
return false;
|
|
}
|
|
|
|
*screen = GRAPHICS_CAPTURE_SCREENS.back();
|
|
GRAPHICS_CAPTURE_SCREENS.pop_back();
|
|
return true;
|
|
}
|
|
|
|
void graphics_capture_enqueue(int screen, uint8_t *data, size_t width, size_t height) {
|
|
GRAPHICS_CAPTURE_BUFFER_M[screen].lock();
|
|
GRAPHICS_CAPTURE_SKIP_SIGNAL[screen] = false;
|
|
auto &capture = GRAPHICS_CAPTURE_BUFFER[screen];
|
|
capture.data.reset(data);
|
|
capture.width = width;
|
|
capture.height = height;
|
|
capture.timestamp = get_performance_milliseconds();
|
|
GRAPHICS_CAPTURE_BUFFER_M[screen].unlock();
|
|
GRAPHICS_CAPTURE_CV[screen].notify_one();
|
|
}
|
|
|
|
void graphics_capture_skip(int screen) {
|
|
if (screen < 0 || screen >= static_cast<int>(GRAPHICS_CAPTURE_SCREEN_NO)) {
|
|
return;
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(GRAPHICS_CAPTURE_BUFFER_M[screen]);
|
|
GRAPHICS_CAPTURE_SKIP_SIGNAL[screen] = true;
|
|
}
|
|
GRAPHICS_CAPTURE_CV[screen].notify_one();
|
|
}
|
|
|
|
bool graphics_capture_receive_jpeg(int screen, TooJpeg::WRITE_ONE_BYTE receiver,
|
|
bool rgb, int quality, bool downsample, int divide, uint64_t *timestamp,
|
|
int *width, int *height) {
|
|
|
|
if (screen < 0 || screen >= static_cast<int>(GRAPHICS_CAPTURE_SCREEN_NO)) {
|
|
return false;
|
|
}
|
|
|
|
// wait for capture event (with timeout)
|
|
std::unique_lock<std::mutex> lock(GRAPHICS_CAPTURE_BUFFER_M[screen]);
|
|
const bool ready = GRAPHICS_CAPTURE_CV[screen].wait_for(
|
|
lock,
|
|
GRAPHICS_CAPTURE_RECEIVE_TIMEOUT,
|
|
[screen] {
|
|
return GRAPHICS_CAPTURE_BUFFER[screen].data != nullptr
|
|
|| GRAPHICS_CAPTURE_SKIP_SIGNAL[screen];
|
|
});
|
|
|
|
if (!ready) {
|
|
lock.unlock();
|
|
graphics_capture_cancel_pending(screen);
|
|
return false;
|
|
}
|
|
|
|
if (GRAPHICS_CAPTURE_SKIP_SIGNAL[screen]) {
|
|
GRAPHICS_CAPTURE_SKIP_SIGNAL[screen] = false;
|
|
lock.unlock();
|
|
return false;
|
|
}
|
|
|
|
auto &capture = GRAPHICS_CAPTURE_BUFFER[screen];
|
|
auto capture_data = capture.data;
|
|
auto capture_width = capture.width;
|
|
auto capture_height = capture.height;
|
|
auto capture_timestamp = capture.timestamp;
|
|
capture.data = nullptr;
|
|
lock.unlock();
|
|
|
|
// validate data
|
|
if (!capture_data || !capture_width || !capture_height) {
|
|
return false;
|
|
}
|
|
|
|
// divide image size
|
|
if (divide > 1) {
|
|
|
|
// get new resolution (round up)
|
|
int width_new = (capture_width + divide - 1) / divide;
|
|
int height_new = (capture_height + divide - 1) / divide;
|
|
|
|
// allocate new data
|
|
auto data_old = capture_data.get();
|
|
auto data_new = new uint8_t[width_new * height_new * 3];
|
|
|
|
// copy pixel data
|
|
int data_y = 0;
|
|
for (int y = 0; y < capture_height; y += divide) {
|
|
int data_y_offset_old = y * capture_width;
|
|
int data_y_offset = data_y * width_new;
|
|
int data_x = 0;
|
|
for (int x = 0; x < capture_width; x += divide) {
|
|
auto pixel_new = &data_new[(data_x + data_y_offset) * 3];
|
|
auto pixel_old = &data_old[(data_y_offset_old + x) * 3];
|
|
memcpy(pixel_new, pixel_old, 3);
|
|
data_x++;
|
|
}
|
|
data_y++;
|
|
}
|
|
|
|
// update capture data
|
|
capture_data.reset(data_new);
|
|
capture_width = width_new;
|
|
capture_height = height_new;
|
|
}
|
|
|
|
// compress
|
|
auto success = TooJpeg::writeJpeg(
|
|
receiver, capture_data.get(),
|
|
capture_width, capture_height,
|
|
rgb, quality, downsample);
|
|
|
|
// status
|
|
if (timestamp) {
|
|
*timestamp = capture_timestamp;
|
|
}
|
|
if (width) {
|
|
*width = capture_width;
|
|
}
|
|
if (height) {
|
|
*height = capture_height;
|
|
}
|
|
|
|
// clean up
|
|
return success;
|
|
}
|
|
|
|
std::string graphics_screenshot_genpath() {
|
|
|
|
// verify dir path
|
|
if (GRAPHICS_SCREENSHOT_DIR.empty()) {
|
|
return "";
|
|
} else {
|
|
auto last_char = GRAPHICS_SCREENSHOT_DIR.back();
|
|
if (last_char == '\\' || last_char == '/') {
|
|
GRAPHICS_SCREENSHOT_DIR.pop_back();
|
|
}
|
|
}
|
|
|
|
// ensure the output directory exists
|
|
if (!fileutils::dir_exists(GRAPHICS_SCREENSHOT_DIR)) {
|
|
if (!fileutils::dir_create_recursive(GRAPHICS_SCREENSHOT_DIR)) {
|
|
log_warning("graphics", "could not create screenshot dir: {}", GRAPHICS_SCREENSHOT_DIR);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// generate date prefix
|
|
auto t_now = std::time(nullptr);
|
|
auto tm_now = *std::gmtime(&t_now);
|
|
auto prefix = to_string(std::put_time(&tm_now, "%Y%m%d"));
|
|
|
|
// find next filename
|
|
size_t id = 0;
|
|
while (true) {
|
|
auto filepath = fmt::format("{}\\{}_{}.png", GRAPHICS_SCREENSHOT_DIR, prefix, id);
|
|
if (!fileutils::file_exists(filepath)) {
|
|
return filepath;
|
|
}
|
|
|
|
id++;
|
|
}
|
|
}
|
|
|
|
static std::string get_dmdo_string(DWORD dmdo) {
|
|
switch (dmdo) {
|
|
case DMDO_DEFAULT:
|
|
return "DMDO_DEFAULT";
|
|
case DMDO_90:
|
|
return "DMDO_90";
|
|
case DMDO_180:
|
|
return "DMDO_180";
|
|
case DMDO_270:
|
|
return "DMDO_270";
|
|
default:
|
|
return fmt::format("Unknown ({})", dmdo);
|
|
}
|
|
}
|
|
|
|
void change_primary_monitor(const std::string &monitor_name) {
|
|
log_misc("graphics", "try changing primary monitor to {}...", monitor_name);
|
|
|
|
// for WinXP, since these are Vista+ or 7+ APIs
|
|
const auto user32 = LoadLibraryA("user32.dll");
|
|
if (!user32) {
|
|
log_warning("graphics", "can't find user32.dll???");
|
|
return;
|
|
}
|
|
const auto GetDisplayConfigBufferSizes_addr =
|
|
reinterpret_cast<decltype(GetDisplayConfigBufferSizes) *>(
|
|
GetProcAddress(user32, "GetDisplayConfigBufferSizes"));
|
|
const auto QueryDisplayConfig_addr =
|
|
reinterpret_cast<decltype(QueryDisplayConfig) *>(
|
|
GetProcAddress(user32, "QueryDisplayConfig"));
|
|
const auto DisplayConfigGetDeviceInfo_addr =
|
|
reinterpret_cast<decltype(DisplayConfigGetDeviceInfo) *>(
|
|
GetProcAddress(user32, "DisplayConfigGetDeviceInfo"));
|
|
SetDisplayConfig_addr =
|
|
reinterpret_cast<decltype(SetDisplayConfig) *>(
|
|
GetProcAddress(user32, "SetDisplayConfig"));
|
|
if (GetDisplayConfigBufferSizes_addr == nullptr || QueryDisplayConfig_addr == nullptr ||
|
|
DisplayConfigGetDeviceInfo_addr == nullptr || SetDisplayConfig_addr == nullptr) {
|
|
log_warning("graphics", "cannot change primary monitor, OS does not support required APIs)");
|
|
return;
|
|
}
|
|
|
|
UINT32 path_count = 0;
|
|
UINT32 mode_count = 0;
|
|
std::vector<DISPLAYCONFIG_PATH_INFO> paths;
|
|
std::vector<DISPLAYCONFIG_MODE_INFO> modes;
|
|
bool succeeded = false;
|
|
|
|
// in a retry loop, try to query for display config
|
|
// retry loop is needed because it can fail with ERROR_INSUFFICIENT_BUFFER
|
|
for (int attempt = 0; attempt < 5; ++attempt) {
|
|
auto status = GetDisplayConfigBufferSizes_addr(QDC_DATABASE_CURRENT, &path_count, &mode_count);
|
|
if (status != ERROR_SUCCESS) {
|
|
log_warning("graphics", "GetDisplayConfigBufferSizes failed: {}", status);
|
|
return;
|
|
}
|
|
|
|
paths.resize(path_count);
|
|
modes.resize(mode_count);
|
|
DISPLAYCONFIG_TOPOLOGY_ID topology_id = DISPLAYCONFIG_TOPOLOGY_INTERNAL;
|
|
status = QueryDisplayConfig_addr(
|
|
QDC_DATABASE_CURRENT,
|
|
&path_count,
|
|
paths.data(),
|
|
&mode_count,
|
|
modes.data(),
|
|
&topology_id);
|
|
|
|
if (status == ERROR_SUCCESS) {
|
|
// Shrink to actual returned counts
|
|
paths.resize(path_count);
|
|
modes.resize(mode_count);
|
|
succeeded = true;
|
|
break;
|
|
}
|
|
|
|
if (status != ERROR_INSUFFICIENT_BUFFER) {
|
|
log_warning("graphics", "QueryDisplayConfig failed: {}", status);
|
|
return;
|
|
}
|
|
|
|
Sleep(500);
|
|
}
|
|
if (!succeeded) {
|
|
log_warning("graphics", "QueryDisplayConfig failed after reaching max retries");
|
|
return;
|
|
}
|
|
|
|
LONG x = 0;
|
|
LONG y = 0;
|
|
bool found = false;
|
|
|
|
// find the new main monitor
|
|
for (auto& mode : modes) {
|
|
if (mode.infoType != DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) {
|
|
continue;
|
|
}
|
|
|
|
DISPLAYCONFIG_SOURCE_DEVICE_NAME name = {};
|
|
name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
|
|
name.header.size = sizeof(name);
|
|
name.header.adapterId = mode.adapterId;
|
|
name.header.id = mode.id;
|
|
if (DisplayConfigGetDeviceInfo_addr(&name.header) != ERROR_SUCCESS) {
|
|
continue;
|
|
}
|
|
|
|
const auto device_name = std::string(ws2s(name.viewGdiDeviceName));
|
|
if (monitor_name == device_name) {
|
|
x = mode.sourceMode.position.x;
|
|
y = mode.sourceMode.position.y;
|
|
found = true;
|
|
log_info(
|
|
"graphics",
|
|
"new main monitor target found: {}, old position: ({}, {})",
|
|
monitor_name,
|
|
x, y);
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
log_fatal(
|
|
"graphics",
|
|
"new main monitor target not found, check -mainmonitor option: {}",
|
|
monitor_name);
|
|
return;
|
|
}
|
|
|
|
// update monitor positions so that the new monitor is at (0, 0)
|
|
for (auto& mode : modes) {
|
|
if (mode.infoType == DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) {
|
|
mode.sourceMode.position.x -= x;
|
|
mode.sourceMode.position.y -= y;
|
|
}
|
|
}
|
|
|
|
// finally, commit the new display config
|
|
const auto status = SetDisplayConfig_addr(
|
|
path_count,
|
|
paths.data(),
|
|
mode_count,
|
|
modes.data(),
|
|
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG);
|
|
|
|
if (status != ERROR_SUCCESS) {
|
|
log_fatal("graphics", "SetDisplayConfig failed, check -mainmonitor option: {}", status);
|
|
}
|
|
|
|
monitor_layout_needs_reset = true;
|
|
|
|
// a little extra time for windows to settle and redraw things
|
|
Sleep(2000);
|
|
}
|
|
|
|
void update_monitor(
|
|
bool is_boot,
|
|
std::optional<graphics_orientation> target_orientation,
|
|
UINT target_refresh_rate,
|
|
std::optional<std::pair<uint32_t, uint32_t>> target_resolution) {
|
|
|
|
// note: all of this is only being done for the primary monitor
|
|
|
|
// get current settings
|
|
DEVMODEA dm = {};
|
|
dm.dmSize = sizeof(dm);
|
|
if (!EnumDisplaySettingsExA(NULL, ENUM_CURRENT_SETTINGS, &dm, 0)) {
|
|
log_warning("graphics", "EnumDisplaySettingsExa failed {}", get_last_error_string());
|
|
return;
|
|
}
|
|
|
|
bool needs_update = false;
|
|
|
|
if (target_orientation.has_value()) {
|
|
// convert orientation values, and figure out if resolution needs to be swapped
|
|
bool rotate_resolution = false;
|
|
DWORD orientation = DMDO_DEFAULT;
|
|
switch (target_orientation.value()) {
|
|
case ORIENTATION_CW:
|
|
orientation = DMDO_90;
|
|
if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) {
|
|
rotate_resolution = true;
|
|
}
|
|
break;
|
|
case ORIENTATION_CCW:
|
|
orientation = DMDO_270;
|
|
if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) {
|
|
rotate_resolution = true;
|
|
}
|
|
break;
|
|
case ORIENTATION_FLIPPED:
|
|
orientation = DMDO_180;
|
|
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) {
|
|
rotate_resolution = true;
|
|
}
|
|
break;
|
|
default:
|
|
orientation = DMDO_DEFAULT;
|
|
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) {
|
|
rotate_resolution = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// update orientation (and resolution if it must be swapped)
|
|
if (dm.dmDisplayOrientation != orientation) {
|
|
log_misc("graphics",
|
|
"current orientation {} => desired orientation {}",
|
|
get_dmdo_string(dm.dmDisplayOrientation), get_dmdo_string(orientation));
|
|
|
|
const DWORD originalWidth = dm.dmPelsWidth;
|
|
const DWORD originalHeight = dm.dmPelsHeight;
|
|
|
|
// change orientation
|
|
dm.dmDisplayOrientation = orientation;
|
|
dm.dmFields |= DM_DISPLAYORIENTATION;
|
|
|
|
// rotate resolution
|
|
if (rotate_resolution) {
|
|
dm.dmPelsWidth = originalHeight;
|
|
dm.dmPelsHeight = originalWidth;
|
|
dm.dmFields |= DM_PELSHEIGHT | DM_PELSWIDTH;
|
|
}
|
|
|
|
needs_update = true;
|
|
}
|
|
}
|
|
|
|
// update refresh rate
|
|
if (target_refresh_rate > 0 && target_refresh_rate != dm.dmDisplayFrequency) {
|
|
log_misc("graphics",
|
|
"current refresh rate {} => desired refresh rate {}",
|
|
dm.dmDisplayFrequency, target_refresh_rate);
|
|
|
|
dm.dmDisplayFrequency = target_refresh_rate;
|
|
dm.dmFields |= DM_DISPLAYFREQUENCY;
|
|
needs_update = true;
|
|
}
|
|
|
|
if (target_resolution.has_value()) {
|
|
if (dm.dmPelsWidth != target_resolution.value().first ||
|
|
dm.dmPelsHeight != target_resolution.value().second) {
|
|
|
|
log_misc(
|
|
"graphics",
|
|
"current resolution {}, {} => desired resolution {}, {}",
|
|
dm.dmPelsWidth, dm.dmPelsHeight,
|
|
target_resolution.value().first, target_resolution.value().second);
|
|
|
|
dm.dmPelsWidth = target_resolution.value().first;
|
|
dm.dmPelsHeight = target_resolution.value().second;
|
|
needs_update = true;
|
|
}
|
|
}
|
|
|
|
if (!needs_update) {
|
|
// nothing to do
|
|
log_misc("graphics", "display settings are already up to date, no changes needed");
|
|
return;
|
|
}
|
|
|
|
const auto result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
|
|
if (result != DISP_CHANGE_SUCCESSFUL) {
|
|
if (is_boot) {
|
|
log_fatal(
|
|
"graphics",
|
|
"failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options",
|
|
dm.dmPelsWidth,
|
|
dm.dmPelsHeight,
|
|
dm.dmDisplayFrequency,
|
|
result);
|
|
} else {
|
|
log_warning(
|
|
"graphics",
|
|
"failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options",
|
|
dm.dmPelsWidth,
|
|
dm.dmPelsHeight,
|
|
dm.dmDisplayFrequency,
|
|
result);
|
|
}
|
|
} else {
|
|
monitor_settings_changed = true;
|
|
// sleep for a little bit after changing monitor settings to delay game launch/resume
|
|
Sleep(1000);
|
|
log_info("graphics", "display settings updated successfully ({}px x {}px @ {}Hz)",
|
|
dm.dmPelsWidth,
|
|
dm.dmPelsHeight,
|
|
dm.dmDisplayFrequency);
|
|
}
|
|
}
|
|
|
|
void update_monitor_on_boot(
|
|
std::optional<graphics_orientation> target_orientation,
|
|
UINT target_refresh_rate,
|
|
std::optional<std::pair<uint32_t, uint32_t>> target_resolution) {
|
|
|
|
target_orientation_on_boot = target_orientation;
|
|
target_refresh_rate_on_boot = target_refresh_rate;
|
|
target_resolution_on_boot = target_resolution;
|
|
log_misc("graphics", "applying monitor updates at boot...");
|
|
update_monitor(
|
|
true,
|
|
target_orientation,
|
|
target_refresh_rate,
|
|
target_resolution);
|
|
}
|
|
|
|
void update_monitor_at_runtime() {
|
|
if (!GRAPHICS_WINDOWED && monitor_settings_changed) {
|
|
log_misc("graphics", "applying monitor updates at runtime as window regained focus...");
|
|
update_monitor(
|
|
false,
|
|
target_orientation_on_boot,
|
|
target_refresh_rate_on_boot,
|
|
target_resolution_on_boot);
|
|
}
|
|
}
|
|
|
|
void reset_monitor_on_exit() {
|
|
|
|
// while CDS_FULLSCREEN is *supposed* to be temporary & the OS attempts to
|
|
// restore the original settings on exit, it can sometimes fail to do that;
|
|
// therefore, we try our best to clean things up on the way out
|
|
if (monitor_settings_changed) {
|
|
monitor_settings_changed = false;
|
|
log_misc("graphics", "resetting monitor settings on exit...");
|
|
ChangeDisplaySettingsW(nullptr, 0);
|
|
}
|
|
|
|
// same for this one.
|
|
if (monitor_layout_needs_reset) {
|
|
monitor_layout_needs_reset = false;
|
|
log_misc("graphics", "restoring primary monitor on exit...");
|
|
if (SetDisplayConfig_addr != nullptr) {
|
|
SetDisplayConfig_addr(
|
|
0,
|
|
nullptr,
|
|
0,
|
|
nullptr,
|
|
SDC_APPLY | SDC_USE_DATABASE_CURRENT);
|
|
}
|
|
}
|
|
}
|