mirror of
https://github.com/optiscaler/OptiScaler.git
synced 2026-08-24 15:16:45 +00:00
Added manual input polling option, should fix menu not working with RTX remix games
This commit is contained in:
@@ -1339,6 +1339,12 @@ CheckForUpdate=auto
|
||||
; true or false - Default (auto) is false, except for OptiFG when true
|
||||
DisableOverlays=auto
|
||||
|
||||
; Use manual input polling instead of hooking WndProc
|
||||
; Might help games which does not capture inputs properly
|
||||
; As a downside it will not be able to block inputs when menu is open
|
||||
; true or false - Default (auto) is false
|
||||
ManualInputPolling=auto
|
||||
|
||||
; Simulate waitable object event signals for FG swapchain
|
||||
; true or false - Default (auto) is false
|
||||
SimulateWaitableObject=auto
|
||||
|
||||
@@ -561,6 +561,7 @@ bool Config::Reload(std::filesystem::path iniPath)
|
||||
{
|
||||
CheckForUpdate.set_from_config(readBool("Hotfix", "CheckForUpdate"));
|
||||
DisableOverlays.set_from_config(readBool("Hotfix", "DisableOverlays"));
|
||||
ManualInputPolling.set_from_config(readBool("Hotfix", "ManualInputPolling"));
|
||||
|
||||
SimulateWaitableObject.set_from_config(readBool("Hotfix", "SimulateWaitableObject"));
|
||||
|
||||
@@ -1199,6 +1200,8 @@ bool Config::SaveIni()
|
||||
ini.SetValue("Hotfix", "SimulateWaitableObject",
|
||||
GetBoolValue(Instance()->SimulateWaitableObject.value_for_config()).c_str());
|
||||
ini.SetValue("Hotfix", "DisableOverlays", GetBoolValue(Instance()->DisableOverlays.value_for_config()).c_str());
|
||||
ini.SetValue("Hotfix", "ManualInputPolling",
|
||||
GetBoolValue(Instance()->ManualInputPolling.value_for_config()).c_str());
|
||||
|
||||
ini.SetValue("Hotfix", "RoundInternalResolution",
|
||||
GetIntValue(Instance()->RoundInternalResolution.value_for_config()).c_str());
|
||||
|
||||
@@ -350,6 +350,7 @@ class Config
|
||||
// Hotfixes
|
||||
CustomOptional<bool> CheckForUpdate { true };
|
||||
CustomOptional<bool> DisableOverlays { false };
|
||||
CustomOptional<bool> ManualInputPolling { false };
|
||||
|
||||
CustomOptional<bool> SimulateWaitableObject { false };
|
||||
|
||||
|
||||
@@ -1247,6 +1247,9 @@ static void printQuirks(flag_set<GameQuirk>& quirks)
|
||||
if (quirks & GameQuirk::PregmataFixDLSSModes)
|
||||
stringQuirks.push_back("Fix DLSS quality selection in Pragmata");
|
||||
|
||||
if (quirks & GameQuirk::UseManualInputs)
|
||||
stringQuirks.push_back("Use manual input polling");
|
||||
|
||||
state->detectedQuirks.append_range(stringQuirks);
|
||||
for (auto& stringQuirk : stringQuirks)
|
||||
spdlog::info("Quirk: {}", stringQuirk);
|
||||
@@ -1500,6 +1503,13 @@ static void CheckQuirks()
|
||||
else
|
||||
quirks.reset(GameQuirk::OldOverlayMenu);
|
||||
|
||||
if (quirks & GameQuirk::UseManualInputs && !Config::Instance()->ManualInputPolling.has_value())
|
||||
{
|
||||
Config::Instance()->ManualInputPolling.set_volatile_value(true);
|
||||
}
|
||||
else
|
||||
quirks.reset(GameQuirk::UseManualInputs);
|
||||
|
||||
// For Luma, we assume if Luma addon in game folder it's used
|
||||
const auto dir = Util::ExePath().parent_path();
|
||||
bool lumaDetected = false;
|
||||
|
||||
+160
-21
@@ -38,6 +38,7 @@ static bool inputMenu = false;
|
||||
static bool inputFG = false;
|
||||
static bool inputFps = false;
|
||||
static bool inputFpsCycle = false;
|
||||
static bool inputManual = false;
|
||||
static bool hasGamepad = false;
|
||||
static bool fsr31InitTried = false;
|
||||
static bool xefgInitTried = false;
|
||||
@@ -133,6 +134,7 @@ static std::string updateNoticeTag;
|
||||
static std::string updateNoticeUrl;
|
||||
static float lastMenuScale = 0.0f;
|
||||
static CustomOptional<uint32_t> comboPreset { 0 };
|
||||
static int lastKey = 0;
|
||||
|
||||
template <typename T, size_t N> struct RingBuffer
|
||||
{
|
||||
@@ -208,6 +210,120 @@ inline std::string StrFmt(const char* fmt, ...)
|
||||
return out;
|
||||
}
|
||||
|
||||
bool IsKeyReleasedOnce(int vk)
|
||||
{
|
||||
static bool previousDown[256] {};
|
||||
|
||||
if (vk <= 0 || vk >= 256)
|
||||
return false;
|
||||
|
||||
bool isDown = (GetAsyncKeyState(vk) & 0x8000) != 0;
|
||||
bool released = previousDown[vk] && !isDown;
|
||||
|
||||
previousDown[vk] = isDown;
|
||||
|
||||
return released;
|
||||
}
|
||||
|
||||
void UpdateManualInput(HWND targetHwnd)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// Only capture input when target window is foreground
|
||||
HWND foreground = GetForegroundWindow();
|
||||
bool focused = foreground == targetHwnd;
|
||||
|
||||
io.AddFocusEvent(focused);
|
||||
|
||||
if (!focused)
|
||||
{
|
||||
io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto config = Config::Instance();
|
||||
|
||||
auto CheckShortcut = [&](int vk, bool& inputFlag, const char* logMessage)
|
||||
{
|
||||
if (inputFlag)
|
||||
return;
|
||||
|
||||
if (vk <= 0 || vk >= 256)
|
||||
return;
|
||||
|
||||
if (IsKeyReleasedOnce(vk))
|
||||
{
|
||||
lastKey = vk;
|
||||
receivingWmInputs = false;
|
||||
inputFlag = true;
|
||||
LOG_DEBUG("{}", logMessage);
|
||||
}
|
||||
};
|
||||
|
||||
CheckShortcut(config->ShortcutKey.value_or_default(), inputMenu, "Menu key pressed, will be switching menu");
|
||||
|
||||
CheckShortcut(config->FpsShortcutKey.value_or_default(), inputFps, "Menu key pressed, will be switching FPS");
|
||||
|
||||
CheckShortcut(config->FGShortcutKey.value_or_default(), inputFG, "Menu key pressed, will be switching FG mode");
|
||||
|
||||
CheckShortcut(config->FpsCycleShortcutKey.value_or_default(), inputFpsCycle,
|
||||
"Menu key pressed, will be switching FPS mode");
|
||||
|
||||
// Mouse position
|
||||
POINT cursorPos {};
|
||||
GetCursorPos(&cursorPos);
|
||||
|
||||
POINT clientPos = cursorPos;
|
||||
ScreenToClient(targetHwnd, &clientPos);
|
||||
|
||||
io.AddMousePosEvent(static_cast<float>(clientPos.x), static_cast<float>(clientPos.y));
|
||||
|
||||
// Mouse buttons
|
||||
io.AddMouseButtonEvent(0, (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0);
|
||||
io.AddMouseButtonEvent(1, (GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0);
|
||||
io.AddMouseButtonEvent(2, (GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0);
|
||||
io.AddMouseButtonEvent(3, (GetAsyncKeyState(VK_XBUTTON1) & 0x8000) != 0);
|
||||
io.AddMouseButtonEvent(4, (GetAsyncKeyState(VK_XBUTTON2) & 0x8000) != 0);
|
||||
|
||||
// Common keyboard keys
|
||||
auto AddKey = [&](ImGuiKey key, int vk) { io.AddKeyEvent(key, (GetAsyncKeyState(vk) & 0x8000) != 0); };
|
||||
|
||||
AddKey(ImGuiKey_Tab, VK_TAB);
|
||||
AddKey(ImGuiKey_LeftArrow, VK_LEFT);
|
||||
AddKey(ImGuiKey_RightArrow, VK_RIGHT);
|
||||
AddKey(ImGuiKey_UpArrow, VK_UP);
|
||||
AddKey(ImGuiKey_DownArrow, VK_DOWN);
|
||||
AddKey(ImGuiKey_PageUp, VK_PRIOR);
|
||||
AddKey(ImGuiKey_PageDown, VK_NEXT);
|
||||
AddKey(ImGuiKey_Home, VK_HOME);
|
||||
AddKey(ImGuiKey_End, VK_END);
|
||||
AddKey(ImGuiKey_Insert, VK_INSERT);
|
||||
AddKey(ImGuiKey_Delete, VK_DELETE);
|
||||
AddKey(ImGuiKey_Backspace, VK_BACK);
|
||||
AddKey(ImGuiKey_Space, VK_SPACE);
|
||||
AddKey(ImGuiKey_Enter, VK_RETURN);
|
||||
AddKey(ImGuiKey_Escape, VK_ESCAPE);
|
||||
|
||||
AddKey(ImGuiKey_LeftCtrl, VK_LCONTROL);
|
||||
AddKey(ImGuiKey_LeftShift, VK_LSHIFT);
|
||||
AddKey(ImGuiKey_LeftAlt, VK_LMENU);
|
||||
AddKey(ImGuiKey_RightCtrl, VK_RCONTROL);
|
||||
AddKey(ImGuiKey_RightShift, VK_RSHIFT);
|
||||
AddKey(ImGuiKey_RightAlt, VK_RMENU);
|
||||
|
||||
// Letters
|
||||
for (int vk = 'A'; vk <= 'Z'; vk++)
|
||||
{
|
||||
io.AddKeyEvent(static_cast<ImGuiKey>(ImGuiKey_A + (vk - 'A')), (GetAsyncKeyState(vk) & 0x8000) != 0);
|
||||
}
|
||||
|
||||
// Numbers
|
||||
for (int vk = '0'; vk <= '9'; vk++)
|
||||
{
|
||||
io.AddKeyEvent(static_cast<ImGuiKey>(ImGuiKey_0 + (vk - '0')), (GetAsyncKeyState(vk) & 0x8000) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuCommon::ShowTooltip(const char* tip)
|
||||
{
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
@@ -637,8 +753,6 @@ ImGuiKey MenuCommon::ImGui_ImplWin32_VirtualKeyToImGuiKey(WPARAM wParam)
|
||||
}
|
||||
}
|
||||
|
||||
static int lastKey = 0;
|
||||
|
||||
class Keybind
|
||||
{
|
||||
std::string name;
|
||||
@@ -1874,6 +1988,9 @@ void MenuCommon::Present()
|
||||
lastFrameTime = now - lastTime;
|
||||
|
||||
lastTime = now;
|
||||
|
||||
if (inputManual && _handle != nullptr)
|
||||
UpdateManualInput(_handle);
|
||||
}
|
||||
|
||||
bool MenuCommon::RenderMenu()
|
||||
@@ -1900,6 +2017,9 @@ bool MenuCommon::RenderMenu()
|
||||
}
|
||||
|
||||
lastTime = now;
|
||||
|
||||
if (inputManual && _handle != nullptr)
|
||||
UpdateManualInput(_handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -7193,35 +7313,54 @@ void MenuCommon::Init(HWND InHwnd, bool isUWP)
|
||||
_hdrTonemapApplied = false;
|
||||
}
|
||||
|
||||
if ((_oWndProc == nullptr || oldHandle != _handle) && !isUWP)
|
||||
DWORD hwndPid = 0;
|
||||
DWORD hwndTid = GetWindowThreadProcessId(_handle, &hwndPid);
|
||||
|
||||
LOG_DEBUG("HWND: {:X}, IsWindow: {}, HWND PID: {}, Current PID: {}, HWND TID: {}, Current TID: {}",
|
||||
(ULONG64) _handle, IsWindow(_handle), hwndPid, GetCurrentProcessId(), hwndTid, GetCurrentThreadId());
|
||||
|
||||
if (hwndPid == GetCurrentProcessId() && !Config::Instance()->ManualInputPolling.value_or_default())
|
||||
{
|
||||
if (oldHandle != nullptr && _oWndProc != nullptr)
|
||||
inputManual = false;
|
||||
|
||||
if ((_oWndProc == nullptr || oldHandle != _handle) && !isUWP)
|
||||
{
|
||||
LOG_DEBUG("Restoring old WndProc: {:X}", (ULONG64) _oWndProc);
|
||||
if (oldHandle != nullptr && _oWndProc != nullptr)
|
||||
{
|
||||
LOG_DEBUG("Restoring old WndProc: {:X}", (ULONG64) _oWndProc);
|
||||
|
||||
SetLastError(0);
|
||||
auto restoreResult = SetWindowLongPtr(oldHandle, GWLP_WNDPROC, (LONG_PTR) _oWndProc);
|
||||
auto error = GetLastError();
|
||||
|
||||
if (restoreResult == 0 && error != 0)
|
||||
{
|
||||
LOG_ERROR("Failed to restore old WndProc. Error: {:X}", error);
|
||||
}
|
||||
}
|
||||
|
||||
SetLastError(0);
|
||||
auto restoreResult = SetWindowLongPtr(oldHandle, GWLP_WNDPROC, (LONG_PTR) _oWndProc);
|
||||
auto setResult = (WNDPROC) SetWindowLongPtr(_handle, GWLP_WNDPROC, (LONG_PTR) WndProc);
|
||||
auto error = GetLastError();
|
||||
|
||||
if (restoreResult == 0 && error != 0)
|
||||
if (setResult == nullptr && error != 0)
|
||||
{
|
||||
LOG_ERROR("Failed to restore old WndProc. Error: {:X}", error);
|
||||
LOG_ERROR("Failed to hook WndProc. Error: {:X}", error);
|
||||
}
|
||||
else
|
||||
{
|
||||
_oWndProc = setResult;
|
||||
LOG_DEBUG("_oWndProc: {:X}", (ULONG64) _oWndProc);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARN("HWND does not belong to current process,"
|
||||
" Manual input polling will be used. HWND PID: {}, Current PID: {}",
|
||||
hwndPid, GetCurrentProcessId());
|
||||
|
||||
SetLastError(0);
|
||||
auto setResult = (WNDPROC) SetWindowLongPtr(_handle, GWLP_WNDPROC, (LONG_PTR) WndProc);
|
||||
auto error = GetLastError();
|
||||
|
||||
if (setResult == nullptr && error != 0)
|
||||
{
|
||||
LOG_ERROR("Failed to hook WndProc. Error: {:X}", error);
|
||||
}
|
||||
else
|
||||
{
|
||||
_oWndProc = setResult;
|
||||
LOG_DEBUG("_oWndProc: {:X}", (ULONG64) _oWndProc);
|
||||
}
|
||||
inputManual = true;
|
||||
}
|
||||
|
||||
if (!pfn_SetCursorPos_hooked)
|
||||
|
||||
@@ -42,6 +42,7 @@ enum class GameQuirk : uint64_t
|
||||
DoNotPreserveFGSwapChain,
|
||||
DoNotSkipResize,
|
||||
OldOverlayMenu,
|
||||
UseManualInputs,
|
||||
|
||||
// Quirks that are applied deeper in code
|
||||
CyberpunkHudlessState,
|
||||
@@ -280,7 +281,7 @@ static const QuirkEntry quirkTable[] = {
|
||||
// Metro Exodus Enhanced Edition
|
||||
// ForceBorderless required to avoid black screen with XeFG
|
||||
QUIRK_ENTRY("metroexodus.exe", GameQuirk::DisableDxgiSpoofing, GameQuirk::ForceBorderlessWhenUsingXeFG,
|
||||
GameQuirk::ForceAutoExposure),
|
||||
GameQuirk::ForceAutoExposure, GameQuirk::UseManualInputs),
|
||||
|
||||
// Star Wars: Outlaws
|
||||
// SL spoof enough to unlock everything DLSS
|
||||
|
||||
Reference in New Issue
Block a user