Skip to content
3 changes: 3 additions & 0 deletions OptiScaler/Source.def
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ CreateDXGIFactory1 = _CreateDXGIFactory1
CreateDXGIFactory2 = _CreateDXGIFactory2
DXGIDeclareAdapterRemovalSupport = _DXGIDeclareAdapterRemovalSupport
DXGIGetDebugInterface1 = _DXGIGetDebugInterface1
ApplyCompatResolutionQuirking = _ApplyCompatResolutionQuirking
CompatString = _CompatString
CompatValue = _CompatValue

EXPORTS
AppCacheCheckManifest = _AppCacheCheckManifest
Expand Down
11 changes: 10 additions & 1 deletion OptiScaler/State.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ enum class SwapchainInteropApi : uint32_t
Dx11wDx12,
};

enum class ColorEncoding : uint32_t
{
SDR,
ScRGB,
PQ,
HLG
};

typedef struct CapturedHudlessInfo
{
UINT64 usageCount = 1;
Expand Down Expand Up @@ -303,7 +311,8 @@ class State

// HDR
std::vector<IUnknown*> scBuffers;
bool isHdrActive = false;
ColorEncoding swapchainEncoding = ColorEncoding::SDR;
bool hdrOutputActive = false;

std::optional<ApiUpscalerInput> setInputApiName;
ApiUpscalerInput currentInputApiName;
Expand Down
6 changes: 2 additions & 4 deletions OptiScaler/hooks/D3D11_Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,12 +334,10 @@ static HRESULT hkD3D11CreateDeviceAndSwapChain(IDXGIAdapter* pAdapter, D3D_DRIVE
}
}

static const D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_1 };

if (!(State::Instance().gameQuirks & GameQuirk::SkipD3D11FeatureLevelElevation))
{
static const D3D_FEATURE_LEVEL levels[] = {
D3D_FEATURE_LEVEL_11_1,
};

D3D_FEATURE_LEVEL maxLevel = D3D_FEATURE_LEVEL_1_0_CORE;

for (UINT i = 0; i < FeatureLevels; ++i)
Expand Down
59 changes: 49 additions & 10 deletions OptiScaler/menu/input/input_system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,12 @@ void ApplyMenuVisibilityChangeLocked(bool visible)
{
EndCursorClipBlockLocked();

// Event-style controller APIs keep their own queues. Drain them before
// releasing the menu block so menu-time events cannot replay into the
// game on the first frame after closing.
DrainDirectInputBufferedDataLocked();
DrainXInputKeystrokesLocked();

_state.HasBlockedCursorScreenPos = false;
_state.BlockedCursorScreenPos = {};

Expand Down Expand Up @@ -763,12 +769,27 @@ bool Initialize(const InitializeOptions& options)
if (options.InputHwnd != nullptr && options.InputHwnd != _state.InputHwnd)
SetInputWindow(options.InputHwnd, options.UseWndProcSubclass, true);

if (!_state.HooksInstalled)
{
LOG_WARN("Initialize re-entry retrying incomplete Win32 hook installation");
_state.HooksInstalled = InstallHooks();

if (_state.HooksInstalled)
{
UpdateGameInputIntegrationLocked();
UpdateXInputIntegrationLocked();
UpdateDirectInputIntegrationLocked();
}
}

LOG_DEBUG(
"Initialize re-entry state target:{} targetPid:{} input:{} inputPid:{} externalTarget:{} subclassed:{}",
"Initialize re-entry state target:{} targetPid:{} input:{} inputPid:{} externalTarget:{} subclassed:{} "
"hooksInstalled:{}",
static_cast<void*>(_state.TargetHwnd), _state.TargetProcessId, static_cast<void*>(_state.InputHwnd),
_state.InputProcessId, _state.ExternalTargetProcess ? 1 : 0, _state.WndProcSubclassed ? 1 : 0);
_state.InputProcessId, _state.ExternalTargetProcess ? 1 : 0, _state.WndProcSubclassed ? 1 : 0,
_state.HooksInstalled ? 1 : 0);

return true;
return _state.HooksInstalled;
}

_state.Initialized = true;
Expand Down Expand Up @@ -1050,14 +1071,32 @@ void Shutdown()
{
std::unique_lock lock(_state.Mutex);

RemoveWindowSubclass();
ReleaseTrackedWindowsHooksLocked();
// Restore cursor confinement and clear the blocking policy before any hook teardown
if (_state.MenuVisible)
ApplyMenuVisibilityChangeLocked(false);
else if (_state.CursorClipReleasedForMenu)
EndCursorClipBlockLocked();

const bool windowSubclassRemoved = RemoveWindowSubclass(true);
const bool trackedWindowsHooksRemoved = ReleaseTrackedWindowsHooksLocked();
RemoveExternalRawInputSinkLocked();
RemoveExternalMouseHookLocked();
RemoveDirectInputHooksLocked();
RemoveXInputHooksLocked();
RemoveGameInputHooksLocked();
RemoveHooks();
const bool externalMouseHookRemoved = RemoveExternalMouseHookLocked();

const bool directInputRemoved = RemoveDirectInputHooksLocked();
const bool xInputRemoved = RemoveXInputHooksLocked();
const bool gameInputRemoved = RemoveGameInputHooksLocked();
const bool win32HooksRemoved = RemoveHooks();

if (!windowSubclassRemoved || !trackedWindowsHooksRemoved || !externalMouseHookRemoved || !directInputRemoved ||
!xInputRemoved || !gameInputRemoved || !win32HooksRemoved)
{
LOG_ERROR("OptiInput shutdown incomplete; retaining state/trampolines for safety wndProc:{} trackedHooks:{} "
"externalMouse:{} dinput:{} xinput:{} gameInput:{} win32:{}",
windowSubclassRemoved ? 1 : 0, trackedWindowsHooksRemoved ? 1 : 0, externalMouseHookRemoved ? 1 : 0,
directInputRemoved ? 1 : 0, xInputRemoved ? 1 : 0, gameInputRemoved ? 1 : 0,
win32HooksRemoved ? 1 : 0);
return;
}

ResetStateAfterShutdown();
}
Expand Down
186 changes: 108 additions & 78 deletions OptiScaler/menu/input/input_system_detours.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <vector>
#include <string_view>

// #define USE_HID_HOOKS

static bool messageHooks = false;
static bool keyStateHooks = false;
static bool getPosHooks = false;
Expand Down Expand Up @@ -358,6 +360,7 @@ bool InstallHooks()
LOG_ERROR("Win32 message hook installation failed result:{}", result);
}

#ifdef USE_HID_HOOKS
if (!State::Instance().isRunningOnLinux && !hidHooks)
{
DetourTransactionBegin();
Expand All @@ -377,6 +380,7 @@ bool InstallHooks()
else
LOG_ERROR("Win32 HID hook installation failed result:{}", result);
}
#endif // USE_HID_HOOKS

if (!rawHooks)
{
Expand Down Expand Up @@ -442,102 +446,128 @@ bool InstallHooks()
if (!positionHooks && !positionIATHooks)
positionIATHooks = InstallCursorIatHooks();

#ifdef USE_HID_HOOKS
const bool hidReady = State::Instance().isRunningOnLinux || hidHooks;
_state.HooksInstalled = messageHooks && keyStateHooks && getPosHooks && clipCursorHooks && message2Hooks &&
hidReady && rawHooks && windowsHooks && (positionHooks || positionIATHooks);
#else
_state.HooksInstalled = messageHooks && keyStateHooks && getPosHooks && clipCursorHooks && message2Hooks &&
hidHooks && rawHooks && windowsHooks && (positionHooks || positionIATHooks);
rawHooks && windowsHooks && (positionHooks || positionIATHooks);
#endif // USE_HID_HOOKS

return _state.HooksInstalled;
}

void RemoveHooks()
bool RemoveHooks()
{
if (!_state.HooksInstalled)
return;
const bool hasDetourHooks = messageHooks || keyStateHooks || getPosHooks || clipCursorHooks || message2Hooks ||
hidHooks || rawHooks || windowsHooks || positionHooks;

if (!hasDetourHooks && !positionIATHooks)
{
_state.HooksInstalled = false;
return true;
}

LOG_INFO("removing Win32 input hooks");

DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
LONG result = NO_ERROR;

if (messageHooks)
if (hasDetourHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_PeekMessageA), hkPeekMessageA);
DetourDetach(reinterpret_cast<PVOID*>(&o_PeekMessageW), hkPeekMessageW);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetMessageA), hkGetMessageA);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetMessageW), hkGetMessageW);
messageHooks = false;
}
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());

if (keyStateHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_GetAsyncKeyState), hkGetAsyncKeyState);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetKeyState), hkGetKeyState);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetKeyboardState), hkGetKeyboardState);
keyStateHooks = false;
}
if (messageHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_PeekMessageA), hkPeekMessageA);
DetourDetach(reinterpret_cast<PVOID*>(&o_PeekMessageW), hkPeekMessageW);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetMessageA), hkGetMessageA);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetMessageW), hkGetMessageW);
}

if (getPosHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_GetMessagePos), hkGetMessagePos);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetMouseMovePointsEx), hkGetMouseMovePointsEx);
getPosHooks = false;
}
if (keyStateHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_GetAsyncKeyState), hkGetAsyncKeyState);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetKeyState), hkGetKeyState);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetKeyboardState), hkGetKeyboardState);
}

if (clipCursorHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_ClipCursor), hkClipCursor);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetClipCursor), hkGetClipCursor);
clipCursorHooks = false;
}
if (getPosHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_GetMessagePos), hkGetMessagePos);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetMouseMovePointsEx), hkGetMouseMovePointsEx);
}

if (message2Hooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_SendInput), hkSendInput);
DetourDetach(reinterpret_cast<PVOID*>(&o_mouse_event), hkmouse_event);
DetourDetach(reinterpret_cast<PVOID*>(&o_PostMessageA), hkPostMessageA);
DetourDetach(reinterpret_cast<PVOID*>(&o_PostMessageW), hkPostMessageW);
DetourDetach(reinterpret_cast<PVOID*>(&o_SendMessageA), hkSendMessageA);
DetourDetach(reinterpret_cast<PVOID*>(&o_SendMessageW), hkSendMessageW);
message2Hooks = false;
}
if (clipCursorHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_ClipCursor), hkClipCursor);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetClipCursor), hkGetClipCursor);
}

if (!State::Instance().isRunningOnLinux && hidHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_CreateFileA), hkCreateFileA);
DetourDetach(reinterpret_cast<PVOID*>(&o_CreateFileW), hkCreateFileW);
DetourDetach(reinterpret_cast<PVOID*>(&o_ReadFile), hkReadFile);
DetourDetach(reinterpret_cast<PVOID*>(&o_DeviceIoControl), hkDeviceIoControl);
DetourDetach(reinterpret_cast<PVOID*>(&o_CloseHandle), hkCloseHandle);
hidHooks = false;
}
if (message2Hooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_SendInput), hkSendInput);
DetourDetach(reinterpret_cast<PVOID*>(&o_mouse_event), hkmouse_event);
DetourDetach(reinterpret_cast<PVOID*>(&o_PostMessageA), hkPostMessageA);
DetourDetach(reinterpret_cast<PVOID*>(&o_PostMessageW), hkPostMessageW);
DetourDetach(reinterpret_cast<PVOID*>(&o_SendMessageA), hkSendMessageA);
DetourDetach(reinterpret_cast<PVOID*>(&o_SendMessageW), hkSendMessageW);
}

if (rawHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_GetRawInputData), hkGetRawInputData);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetRawInputBuffer), hkGetRawInputBuffer);
DetourDetach(reinterpret_cast<PVOID*>(&o_RegisterRawInputDevices), hkRegisterRawInputDevices);
rawHooks = false;
}
if (hidHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_CreateFileA), hkCreateFileA);
DetourDetach(reinterpret_cast<PVOID*>(&o_CreateFileW), hkCreateFileW);
DetourDetach(reinterpret_cast<PVOID*>(&o_ReadFile), hkReadFile);
DetourDetach(reinterpret_cast<PVOID*>(&o_DeviceIoControl), hkDeviceIoControl);
DetourDetach(reinterpret_cast<PVOID*>(&o_CloseHandle), hkCloseHandle);
}

if (windowsHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_SetWindowsHookExA), hkSetWindowsHookExA);
DetourDetach(reinterpret_cast<PVOID*>(&o_SetWindowsHookExW), hkSetWindowsHookExW);
DetourDetach(reinterpret_cast<PVOID*>(&o_UnhookWindowsHookEx), hkUnhookWindowsHookEx);
windowsHooks = false;
}
if (rawHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_GetRawInputData), hkGetRawInputData);
DetourDetach(reinterpret_cast<PVOID*>(&o_GetRawInputBuffer), hkGetRawInputBuffer);
DetourDetach(reinterpret_cast<PVOID*>(&o_RegisterRawInputDevices), hkRegisterRawInputDevices);
}

if (positionHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_GetCursorPos), hkGetCursorPos);
DetourDetach(reinterpret_cast<PVOID*>(&o_SetCursorPos), hkSetCursorPos);
if (windowsHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_SetWindowsHookExA), hkSetWindowsHookExA);
DetourDetach(reinterpret_cast<PVOID*>(&o_SetWindowsHookExW), hkSetWindowsHookExW);
DetourDetach(reinterpret_cast<PVOID*>(&o_UnhookWindowsHookEx), hkUnhookWindowsHookEx);
}

if (o_GetPhysicalCursorPos != nullptr)
DetourDetach(reinterpret_cast<PVOID*>(&o_GetPhysicalCursorPos), hkGetPhysicalCursorPos);
if (positionHooks)
{
DetourDetach(reinterpret_cast<PVOID*>(&o_GetCursorPos), hkGetCursorPos);
DetourDetach(reinterpret_cast<PVOID*>(&o_SetCursorPos), hkSetCursorPos);

if (o_SetPhysicalCursorPos != nullptr)
DetourDetach(reinterpret_cast<PVOID*>(&o_SetPhysicalCursorPos), hkSetPhysicalCursorPos);
if (o_GetPhysicalCursorPos != nullptr)
DetourDetach(reinterpret_cast<PVOID*>(&o_GetPhysicalCursorPos), hkGetPhysicalCursorPos);

if (o_SetPhysicalCursorPos != nullptr)
DetourDetach(reinterpret_cast<PVOID*>(&o_SetPhysicalCursorPos), hkSetPhysicalCursorPos);
}

positionHooks = false;
result = DetourTransactionCommit();

if (result == NO_ERROR)
{
messageHooks = false;
keyStateHooks = false;
getPosHooks = false;
clipCursorHooks = false;
message2Hooks = false;
hidHooks = false;
rawHooks = false;
windowsHooks = false;
positionHooks = false;
}
else
{
LOG_WARN("Win32 input hook removal failed result:{}; retaining hook state for a safe retry", result);
}
}

if (positionIATHooks)
Expand All @@ -546,11 +576,11 @@ void RemoveHooks()
positionIATHooks = false;
}

const LONG result = DetourTransactionCommit();
if (result != NO_ERROR)
LOG_WARN("Win32 input hook removal completed with result:{}", result);
const bool hidReady = State::Instance().isRunningOnLinux || hidHooks;
_state.HooksInstalled = messageHooks && keyStateHooks && getPosHooks && clipCursorHooks && message2Hooks &&
hidReady && rawHooks && windowsHooks && (positionHooks || positionIATHooks);

_state.HooksInstalled = false;
return result == NO_ERROR;
}

} // namespace OptiInput
Loading