@@ -15,7 +15,32 @@ namespace Addictol
1515 const DXGI_SWAP_CHAIN_DESC*, IDXGISwapChain**,
1616 ID3D11Device**, D3D_FEATURE_LEVEL*, ID3D11DeviceContext**);
1717
18+ using TSetFullscreenState = HRESULT(WINAPI*)(IDXGISwapChain*, BOOL, IDXGIOutput*);
19+
1820 static TD3D11CreateDeviceAndSwapChain OriginalCreate = nullptr ;
21+ static std::atomic<TSetFullscreenState> OriginalSetFullscreenState{ nullptr };
22+ static std::atomic<HWND> g_gameHwnd{ nullptr };
23+ static std::atomic<bool > g_vtablePatched{ false };
24+
25+ static HRESULT WINAPI Hook_SetFullscreenState (IDXGISwapChain* a_this, BOOL a_fullscreen, IDXGIOutput* a_output) noexcept
26+ {
27+ const auto orig = OriginalSetFullscreenState.load (std::memory_order_acquire);
28+
29+ // Only neuter our own swap chain; ReShade/ENB/overlay swap chains get forwarded.
30+ if (a_fullscreen)
31+ {
32+ DXGI_SWAP_CHAIN_DESC desc{};
33+ const auto descHr = a_this->GetDesc (&desc);
34+ const auto gameHwnd = g_gameHwnd.load (std::memory_order_relaxed);
35+ if (SUCCEEDED (descHr) && desc.OutputWindow == gameHwnd && gameHwnd)
36+ {
37+ REX::INFO (" Alt-Tab Fullscreen: SetFullscreenState(TRUE) blocked on game window." sv);
38+ return S_OK;
39+ }
40+ }
41+
42+ return orig ? orig (a_this, a_fullscreen, a_output) : S_OK;
43+ }
1944
2045 static HRESULT WINAPI Hook_D3D11Create (
2146 IDXGIAdapter* a_adapter,
@@ -58,8 +83,38 @@ namespace Addictol
5883 IDXGIFactory* factory = nullptr ;
5984 if (SUCCEEDED ((*a_outSwapChain)->GetParent (__uuidof (IDXGIFactory), reinterpret_cast <void **>(&factory))) && factory)
6085 {
61- factory->MakeWindowAssociation (descToUse->OutputWindow , DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER);
86+ const auto mwaHr = factory->MakeWindowAssociation (descToUse->OutputWindow , DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER);
6287 factory->Release ();
88+ if (FAILED (mwaHr))
89+ {
90+ REX::WARN (" Alt-Tab Fullscreen: MakeWindowAssociation failed (hr=0x{:08X})." sv, static_cast <std::uint32_t >(mwaHr));
91+ }
92+ }
93+
94+ // MakeWindowAssociation isn't enough; explicit SetFullscreenState(TRUE) bypasses it.
95+ g_gameHwnd.store (descToUse->OutputWindow , std::memory_order_relaxed);
96+
97+ bool expected = false ;
98+ if (g_vtablePatched.compare_exchange_strong (expected, true ))
99+ {
100+ constexpr std::uint32_t kSetFullscreenStateSlot = 10 ;
101+ auto ** const vtablePtr = *reinterpret_cast <void ***>(*a_outSwapChain);
102+ // Publish original before the swap so the forward path never sees nullptr.
103+ OriginalSetFullscreenState.store (
104+ reinterpret_cast <TSetFullscreenState>(vtablePtr[kSetFullscreenStateSlot ]),
105+ std::memory_order_release);
106+
107+ const auto vtableAddr = reinterpret_cast <std::uintptr_t >(vtablePtr);
108+ const auto returned = RELEX::DetourVTable (
109+ vtableAddr,
110+ reinterpret_cast <std::uintptr_t >(&Hook_SetFullscreenState),
111+ kSetFullscreenStateSlot );
112+ if (!returned)
113+ {
114+ REX::WARN (" Alt-Tab Fullscreen: SetFullscreenState vtable patch failed." sv);
115+ OriginalSetFullscreenState.store (nullptr , std::memory_order_release);
116+ g_vtablePatched.store (false );
117+ }
63118 }
64119
65120 return hr;
0 commit comments