PageRenderTime 24ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/ImGuiOverlay.cpp

https://gitlab.com/mxdog/mqnext-mxdog
C++ | 1382 lines | 983 code | 277 blank | 122 comment | 185 complexity | 65e6f8f7c400aa7c4f62152712b6718a MD5 | raw file
  1. /*
  2. * MacroQuest: The extension platform for EverQuest
  3. * Copyright (C) 2002-2022 MacroQuest Authors
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License, version 2, as published by
  7. * the Free Software Foundation.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. */
  14. #include "pch.h"
  15. #include "MQ2Main.h"
  16. #include "MQ2DeveloperTools.h"
  17. #include "CrashHandler.h"
  18. #include "ImGuiManager.h"
  19. #include "ImGuiBackend.h"
  20. #include "common/HotKeys.h"
  21. #include "imgui/ImGuiUtils.h"
  22. #include <imgui.h>
  23. #include <imgui_stdlib.h>
  24. #include <cfenv>
  25. #include <spdlog/spdlog.h>
  26. #include <fmt/format.h>
  27. #include <atomic>
  28. #include <vector>
  29. #include <wil/com.h>
  30. namespace mq {
  31. //----------------------------------------------------------------------------
  32. // globals
  33. // The global direct 3d device that we are using. This belongs to EQ, so we
  34. // need to hook into it to understand when states change.
  35. IDirect3DDevice9* gpD3D9Device = nullptr;
  36. // Indicates that we've acquired the device and its usable
  37. bool gbDeviceAcquired = false;
  38. // Indicates that the graphics device hooks are installed
  39. bool gbDeviceHooksInstalled = false;
  40. // Indicates that we've initialized the overlay
  41. bool gbOverlayInitialized = false;
  42. // Indicates that we need to reset the overlay next frame
  43. bool gbNeedResetOverlay = false;
  44. // If true, overlay functionality is disabled
  45. bool gbDisableOverlay = false;
  46. // Last known full screen state
  47. bool gbLastFullScreenState = false;
  48. // Number of frames to wait before initializing
  49. int gReInitFrameDelay = 0;
  50. bool gbImGuiReady = false;
  51. //----------------------------------------------------------------------------
  52. // statics
  53. static uintptr_t gResetDeviceAddress = 0;
  54. static POINT gMouseLocation;
  55. static bool gbMouseBlocked = false;
  56. static char ImGuiSettingsFile[MAX_PATH] = { 0 };
  57. static bool gbFlushNextMouse = false;
  58. static bool gbRetryHooks = false;
  59. static bool gbInitializationFailed = false;
  60. static bool gbInitializedImGui = false;
  61. static int gLastGameState = GAMESTATE_PRECHARSELECT;
  62. static int s_numBeginSceneCalls = 0;
  63. using D3D9CREATEEXPROC = HRESULT(WINAPI*)(UINT, IDirect3D9Ex**);
  64. // Record for tracking renderer callbacks
  65. struct MQRenderCallbackRecord
  66. {
  67. MQRenderCallbacks callbacks;
  68. int id;
  69. };
  70. static std::vector<std::unique_ptr<MQRenderCallbackRecord>> s_renderCallbacks;
  71. // Forward declarations
  72. LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
  73. void ImGuiRenderDebug_UpdateRenderTargets();
  74. static bool s_enableImGuiViewports = false;
  75. static bool s_enableImGuiDocking = true;
  76. static bool s_deferredClearSettings = false;
  77. //============================================================================
  78. // Detour helpers
  79. // Some of our hooks are determined dynamically. Rather than have a variable
  80. // for each one, we store them in a collection of these HookInfo objects.
  81. struct HookInfo
  82. {
  83. std::string name;
  84. uintptr_t address = 0;
  85. std::function<void(HookInfo&)> patch = nullptr;
  86. };
  87. // List of installed hooks at their address, if they are already patched in
  88. static std::vector<HookInfo> gHooks;
  89. static void InstallHook(HookInfo hi)
  90. {
  91. auto iter = std::find_if(std::begin(gHooks), std::end(gHooks),
  92. [&hi](const HookInfo& hookInfo)
  93. {
  94. return hi.name == hookInfo.name;
  95. });
  96. if (iter != std::end(gHooks))
  97. {
  98. // hook already installed. just skip.
  99. return;
  100. }
  101. hi.patch(hi);
  102. gHooks.push_back(hi);
  103. }
  104. template <typename T>
  105. static void InstallDetour(uintptr_t address, T detour, T& trampoline_ptr, PCHAR name)
  106. {
  107. HookInfo hookInfo;
  108. hookInfo.name = name;
  109. hookInfo.address = 0;
  110. hookInfo.patch = [&detour, address, &trampoline_ptr](HookInfo& hi)
  111. {
  112. hi.address = address;
  113. mq::detail::CreateDetour(hi.address, &(void*&)trampoline_ptr, *(void**)&detour, hi.name);
  114. };
  115. InstallHook(hookInfo);
  116. }
  117. static void RemoveDetours()
  118. {
  119. for (HookInfo& hook : gHooks)
  120. {
  121. if (hook.address != 0)
  122. {
  123. RemoveDetour(hook.address);
  124. hook.address = 0;
  125. }
  126. }
  127. }
  128. //============================================================================
  129. // Exported
  130. int AddRenderCallbacks(const MQRenderCallbacks& callbacks)
  131. {
  132. // Find an unused index.
  133. int index = -1;
  134. for (int i = 0; i < (int)s_renderCallbacks.size(); ++i)
  135. {
  136. if (s_renderCallbacks[i] == nullptr)
  137. {
  138. index = i;
  139. break;
  140. }
  141. }
  142. if (index == -1)
  143. {
  144. s_renderCallbacks.emplace_back();
  145. index = static_cast<int>(s_renderCallbacks.size()) - 1;
  146. }
  147. auto pCallbacks = std::make_unique<MQRenderCallbackRecord>();
  148. pCallbacks->callbacks = callbacks;
  149. pCallbacks->id = index;
  150. // Make sure that we initialize if we're already acquired by
  151. // calling CreateDeviceObjects.
  152. if (gbDeviceAcquired && pCallbacks->callbacks.CreateDeviceObjects)
  153. {
  154. pCallbacks->callbacks.CreateDeviceObjects();
  155. }
  156. s_renderCallbacks[index] = std::move(pCallbacks);
  157. return index;
  158. }
  159. // Exported
  160. void RemoveRenderCallbacks(uint32_t id)
  161. {
  162. if (id >= 0 && id < s_renderCallbacks.size())
  163. {
  164. // not sure if we should do this here or in the calling plugin...
  165. if (s_renderCallbacks[id] && s_renderCallbacks[id]->callbacks.InvalidateDeviceObjects)
  166. {
  167. s_renderCallbacks[id]->callbacks.InvalidateDeviceObjects();
  168. }
  169. s_renderCallbacks[id].reset();
  170. }
  171. }
  172. static void Renderer_InvalidateDeviceObjects()
  173. {
  174. SPDLOG_DEBUG("MQ2Overlay: InvalidateDeviceObjects");
  175. gbDeviceAcquired = false;
  176. ImGui_ImplDX9_InvalidateDeviceObjects();
  177. gbImGuiReady = false;
  178. for (const auto& pCallbacks : s_renderCallbacks)
  179. {
  180. if (pCallbacks && pCallbacks->callbacks.InvalidateDeviceObjects)
  181. {
  182. pCallbacks->callbacks.InvalidateDeviceObjects();
  183. }
  184. }
  185. }
  186. static bool Renderer_CreateDeviceObjects()
  187. {
  188. SPDLOG_DEBUG("MQ2Overlay: CreateDeviceObjects");
  189. gbDeviceAcquired = true;
  190. gbImGuiReady = ImGui_ImplDX9_CreateDeviceObjects();
  191. for (const auto& pCallbacks : s_renderCallbacks)
  192. {
  193. if (pCallbacks && pCallbacks->callbacks.CreateDeviceObjects)
  194. {
  195. pCallbacks->callbacks.CreateDeviceObjects();
  196. }
  197. }
  198. return gbImGuiReady;
  199. }
  200. static void Renderer_UpdateScene()
  201. {
  202. if (s_renderCallbacks.empty())
  203. return;
  204. // Perform the render within a stateblock so we don't upset the rest
  205. // of the rendering pipeline
  206. wil::com_ptr_nothrow<IDirect3DStateBlock9> stateBlock;
  207. gpD3D9Device->CreateStateBlock(D3DSBT_ALL, &stateBlock);
  208. for (const auto& pCallbacks : s_renderCallbacks)
  209. {
  210. if (pCallbacks && pCallbacks->callbacks.GraphicsSceneRender)
  211. {
  212. pCallbacks->callbacks.GraphicsSceneRender();
  213. }
  214. }
  215. stateBlock->Apply();
  216. }
  217. //============================================================================
  218. // Returns true if this event should be "erased" from eq while not within the imgui
  219. // capture. This lets us intercepts clicks into the game world.
  220. bool ImGuiOverlay_HandleMouseEvent(int mouseButton, bool pressed)
  221. {
  222. ImGuiIO& io = ImGui::GetIO();
  223. bool consume = false;
  224. if (test_and_set(io.MouseDown[mouseButton], pressed))
  225. {
  226. if (!io.WantCaptureMouse)
  227. {
  228. if (DeveloperTools_WindowInspector_HandleClick(mouseButton, pressed))
  229. consume = true;
  230. }
  231. }
  232. if (consume && mouseButton < NUM_MOUSE_BUTTONS)
  233. {
  234. // Update EQ to act like we already handled this click
  235. pEverQuestInfo->OldMouseButtons[mouseButton] = pressed;
  236. pEverQuestInfo->MouseButtons[mouseButton] = pressed;
  237. }
  238. return consume;
  239. }
  240. static bool IsFullScreen(IDirect3DDevice9* device)
  241. {
  242. if (!device)
  243. return false;
  244. // Detect full screen and disable viewports if we're in full screen mode.
  245. bool fullscreen = true;
  246. wil::com_ptr_nothrow<IDirect3DSwapChain9> pSwapChain;
  247. if (SUCCEEDED(device->GetSwapChain(0, &pSwapChain)) && pSwapChain != nullptr)
  248. {
  249. D3DPRESENT_PARAMETERS params;
  250. pSwapChain->GetPresentParameters(&params);
  251. fullscreen = !params.Windowed;
  252. }
  253. return fullscreen;
  254. }
  255. static void InitializeImGui(IDirect3DDevice9* device)
  256. {
  257. if (gbInitializedImGui)
  258. return;
  259. // Enable Multi-Viewport / Platform Windows
  260. gbLastFullScreenState = IsFullScreen(device);
  261. ImGui_EnableViewports(!gbLastFullScreenState && s_enableImGuiViewports);
  262. // Enable Docking
  263. ImGui_EnableDocking(s_enableImGuiDocking);
  264. // Retrieve window handle from device
  265. D3DDEVICE_CREATION_PARAMETERS params;
  266. device->GetCreationParameters(&params);
  267. // Initialize the platform backend and renderer bindings
  268. ImGui_ImplWin32_Init(params.hFocusWindow);
  269. ImGui_ImplDX9_Init(device);
  270. gbInitializedImGui = true;
  271. }
  272. void ShutdownImGui()
  273. {
  274. if (!gbInitializedImGui)
  275. return;
  276. ImGui_ImplDX9_Shutdown();
  277. ImGui_ImplWin32_Shutdown();
  278. gbImGuiReady = false;
  279. gbInitializedImGui = false;
  280. }
  281. static bool RenderImGui()
  282. {
  283. using namespace imgui;
  284. if (!gbImGuiReady)
  285. return false;
  286. if (gbNeedResetOverlay)
  287. return false;
  288. if (!gpD3D9Device)
  289. return false;
  290. // This is loading/transitioning screen
  291. if (gGameState == GAMESTATE_LOGGINGIN)
  292. return false;
  293. ImGuiManager_DrawFrame();
  294. return true;
  295. }
  296. //============================================================================
  297. //============================================================================
  298. class RenderHooks
  299. {
  300. public:
  301. //------------------------------------------------------------------------
  302. // d3d9 hooks
  303. // this is only valid during a d3d9 hook detour
  304. IDirect3DDevice9* GetThisDevice() { return reinterpret_cast<IDirect3DDevice9*>(this); }
  305. // Install hooks on actual instance of the device once we have it.
  306. bool DetectResetDeviceHook()
  307. {
  308. bool changed = false;
  309. // IDirect3DDevice9 virtual function hooks
  310. uintptr_t* d3dDevice_vftable = *(uintptr_t**)this;
  311. uintptr_t resetDevice = d3dDevice_vftable[0x10];
  312. if (resetDevice != gResetDeviceAddress)
  313. {
  314. if (gResetDeviceAddress != 0)
  315. {
  316. SPDLOG_DEBUG("Detected a change in the rendering device. Attempting to recover.");
  317. }
  318. gResetDeviceAddress = resetDevice;
  319. InstallDetour(d3dDevice_vftable[0x10], &RenderHooks::Reset_Detour, RenderHooks::Reset_Trampoline_Ptr, "d3dDevice_Reset");
  320. changed = true;
  321. }
  322. return changed;
  323. }
  324. DETOUR_TRAMPOLINE_DEF(HRESULT WINAPI, Reset_Trampoline, (D3DPRESENT_PARAMETERS*))
  325. HRESULT WINAPI Reset_Detour(D3DPRESENT_PARAMETERS* pPresentationParameters)
  326. {
  327. if (gpD3D9Device != GetThisDevice())
  328. {
  329. SPDLOG_INFO("IDirect3DDevice9::Reset hook: instance does not match acquired device, skipping.");
  330. return Reset_Trampoline(pPresentationParameters);
  331. }
  332. SPDLOG_INFO("IDirect3DDevice9::Reset hook: device instance is the acquired device.");
  333. Renderer_InvalidateDeviceObjects();
  334. return Reset_Trampoline(pPresentationParameters);
  335. }
  336. DETOUR_TRAMPOLINE_DEF(HRESULT WINAPI, BeginScene_Trampoline, ())
  337. HRESULT WINAPI BeginScene_Detour()
  338. {
  339. // Whenever a BeginScene occurs, we know that this is the device we want to use.
  340. gpD3D9Device = GetThisDevice();
  341. s_numBeginSceneCalls++;
  342. return BeginScene_Trampoline();
  343. }
  344. DETOUR_TRAMPOLINE_DEF(HRESULT WINAPI, EndScene_Trampoline, ())
  345. HRESULT WINAPI EndScene_Detour()
  346. {
  347. // Don't try to use the device if it changed between BeginScene and EndScene.
  348. if (GetThisDevice() != gpD3D9Device || !gpD3D9Device)
  349. {
  350. return EndScene_Trampoline();
  351. }
  352. // Prevent re-entrancy. This was happening due to the mumble overlay.
  353. static bool sbInEndSceneDetour = false;
  354. if (sbInEndSceneDetour)
  355. {
  356. return EndScene_Trampoline();
  357. }
  358. bool isMainRenderTarget = false;
  359. // Make sure that we're hooking the main render target
  360. wil::com_ptr_nothrow<IDirect3DSurface9> backBuffer;
  361. HRESULT hr = gpD3D9Device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer);
  362. if (hr == D3D_OK)
  363. {
  364. wil::com_ptr_nothrow<IDirect3DSurface9> renderTarget = nullptr;
  365. HRESULT hr = gpD3D9Device->GetRenderTarget(0, &renderTarget);
  366. if (hr == D3D_OK)
  367. {
  368. isMainRenderTarget = renderTarget == backBuffer;
  369. }
  370. }
  371. sbInEndSceneDetour = true;
  372. if (isMainRenderTarget)
  373. {
  374. // Check if a full screen mode change occurred before this frame.
  375. // If it did, we should not render, and instead re-initialize imgui.
  376. if (test_and_set(gbLastFullScreenState, IsFullScreen(gpD3D9Device)))
  377. {
  378. // For some reason, maybe due to a bug, toggling viewports off and
  379. // then calling CreateDeviceObjects will cause it to still create
  380. // the additional swap chains, which causes errors. So instead of
  381. // disabling viewports, we are simply rebooting imgui.
  382. gbNeedResetOverlay = true;
  383. }
  384. // When TestCooperativeLevel returns all good, then we can reinitialize.
  385. // This will let the renderer control our flow instead of having to
  386. // poll for the state ourselves.
  387. else if (!gbDeviceAcquired)
  388. {
  389. if (gReInitFrameDelay == 0)
  390. {
  391. HRESULT result = GetThisDevice()->TestCooperativeLevel();
  392. if (result == D3D_OK)
  393. {
  394. SPDLOG_INFO("IDirect3DDevice9::EndScene: TestCooperativeLevel was successful, reacquiring device.");
  395. InitializeImGui(gpD3D9Device);
  396. if (DetectResetDeviceHook())
  397. {
  398. Renderer_InvalidateDeviceObjects();
  399. }
  400. Renderer_CreateDeviceObjects();
  401. }
  402. }
  403. else
  404. {
  405. --gReInitFrameDelay;
  406. }
  407. }
  408. // Perform the render within a stateblock so we don't upset the
  409. // rest of the rendering pipeline
  410. if (gbDeviceAcquired)
  411. {
  412. RenderImGui();
  413. }
  414. }
  415. HRESULT result = EndScene_Trampoline();
  416. if (isMainRenderTarget && gbDeviceAcquired)
  417. {
  418. ImGuiRenderDebug_UpdateRenderTargets();
  419. }
  420. sbInEndSceneDetour = false;
  421. return result;
  422. }
  423. };
  424. // This hooks into an area of the rendering pipeline that is suitable to perform
  425. // our own 3d rendering before the scene is completed.
  426. class CParticleSystemHook
  427. {
  428. public:
  429. DETOUR_TRAMPOLINE_DEF(void, Render_Trampoline, ())
  430. void Render_Detour()
  431. {
  432. if (gbDeviceAcquired)
  433. {
  434. Renderer_UpdateScene();
  435. }
  436. Render_Trampoline();
  437. }
  438. };
  439. class CRenderHook
  440. {
  441. public:
  442. // This hooks the ResetDevice function which is called when we want to reset the device. Hooking
  443. // this function ensures that even if our hook of the Direct3D device fails, we can still release our objects.
  444. DETOUR_TRAMPOLINE_DEF(bool, ResetDevice_Trampoline, (bool))
  445. bool ResetDevice_Detour(bool a)
  446. {
  447. SPDLOG_DEBUG("CRender::ResetDevice: Resetting device");
  448. bool success = ResetDevice_Trampoline(a);
  449. if (!success)
  450. {
  451. SPDLOG_DEBUG("CRender::ResetDevice: Reset failed, invalidating device objects and trying again.");
  452. Renderer_InvalidateDeviceObjects();
  453. success = ResetDevice_Trampoline(a);
  454. }
  455. SPDLOG_DEBUG("CRender::ResetDevice: result={}", success);
  456. return success;
  457. }
  458. };
  459. // Mouse hook prevents mouse events from reaching EQ when imgui captures
  460. // the mouse. Needed because ImGui uses win32 events but EQ uses direct input.
  461. DETOUR_TRAMPOLINE_DEF(void, ProcessMouseEvents_Trampoline, ())
  462. void ProcessMouseEvents_Detour()
  463. {
  464. if (ImGui::GetCurrentContext() != nullptr && *EQADDR_DIMOUSE != nullptr)
  465. {
  466. auto pMouse = (*EQADDR_DIMOUSE);
  467. ImGuiIO& io = ImGui::GetIO();
  468. bool consumeMouse = io.WantCaptureMouse;
  469. // Read mouse state from direct input
  470. DIDEVICEOBJECTDATA data[128];
  471. DWORD num = 128;
  472. HRESULT hr = pMouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), data, &num, DIGDD_PEEK);
  473. if (SUCCEEDED(hr))
  474. {
  475. for (DWORD i = 0; i < num; i++)
  476. {
  477. DIDEVICEOBJECTDATA& d = data[i];
  478. switch (d.dwOfs)
  479. {
  480. case DIMOFS_BUTTON0:
  481. case DIMOFS_BUTTON1:
  482. case DIMOFS_BUTTON2:
  483. case DIMOFS_BUTTON3:
  484. case DIMOFS_BUTTON4:
  485. if (ImGuiOverlay_HandleMouseEvent(d.dwOfs - DIMOFS_BUTTON0, (d.dwData & 0x80) != 0))
  486. consumeMouse = true;
  487. break;
  488. default:
  489. if (d.dwOfs == DIMOFS_Z)
  490. {
  491. io.MouseWheel += static_cast<int>(d.dwData) / (float)WHEEL_DELTA;
  492. }
  493. break;
  494. }
  495. }
  496. }
  497. if (consumeMouse)
  498. {
  499. pEverQuestInfo->MouseInClientRect = 0;
  500. gbFlushNextMouse = true;
  501. // Consume the mouse state. This won't be very effective for sustained mouse clicks though.
  502. pMouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), data, &num, 0);
  503. return;
  504. }
  505. }
  506. // Its time to detour GetDeviceState and have it return the inputs from WndProc that we didn't handle.
  507. if (gbFlushNextMouse && *EQADDR_DIMOUSE != nullptr)
  508. {
  509. auto pMouse = (*EQADDR_DIMOUSE);
  510. // Flush the direct input device state so that the wheel data doesn't get
  511. // picked up by the game later.
  512. DIDEVICEOBJECTDATA data[128];
  513. DWORD num = 128;
  514. pMouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), data, &num, 0);
  515. DIMOUSESTATE2 dims;
  516. pMouse->GetDeviceState(sizeof(DIMOUSESTATE2), &dims);
  517. }
  518. ProcessMouseEvents_Trampoline();
  519. // When we flush the mouse we want it to affect the mouse wheel too. HandleMouseWheel
  520. // happens inside the trampoline, so we wait to clear the flag until after.
  521. gbFlushNextMouse = false;
  522. }
  523. #if defined(__HandleMouseWheel_x)
  524. // The mouse wheel hook prevents EQ from handling scroll events while imgui has the
  525. // mouse captured.
  526. DETOUR_TRAMPOLINE_DEF(void, HandleMouseWheel_Trampoline, (int))
  527. void HandleMouseWheel_Detour(int offset)
  528. {
  529. if (ImGui::GetCurrentContext() != nullptr)
  530. {
  531. ImGuiIO& io = ImGui::GetIO();
  532. if (io.WantCaptureMouse)
  533. {
  534. return;
  535. }
  536. }
  537. if (!gbFlushNextMouse)
  538. {
  539. HandleMouseWheel_Trampoline(offset);
  540. }
  541. }
  542. #endif // defined(__HandleMouseWheel_x)
  543. // Keyboard hook prevents keyboard events from reaching EQ when imgui captures
  544. // the keyboard. Needed because ImGui uses win32 events but EQ uses direct input.
  545. DETOUR_TRAMPOLINE_DEF(uint32_t, ProcessKeyboardEvents_Trampoline, ())
  546. uint32_t ProcessKeyboardEvents_Detour()
  547. {
  548. if (ImGui::GetCurrentContext() != nullptr)
  549. {
  550. ImGuiIO& io = ImGui::GetIO();
  551. if (io.WantCaptureKeyboard)
  552. {
  553. // FlushDxKeyboard will consume any keyboard data that is currently
  554. // buffered by direct input so that the keyboard inputs don't get
  555. // picked up by EQ too.
  556. eqlib::FlushDxKeyboard();
  557. return 0;
  558. }
  559. }
  560. return ProcessKeyboardEvents_Trampoline();
  561. }
  562. bool OverlayWndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
  563. {
  564. if (ImGuiManager_HandleWndProc(msg, wParam, lParam))
  565. return true;
  566. if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
  567. return true;
  568. return false;
  569. }
  570. // Forwards events to ImGui. If ImGui consumes the event, we won't pass it to the game.
  571. DETOUR_TRAMPOLINE_DEF(LRESULT WINAPI, WndProc_Trampoline, (HWND, UINT, WPARAM, LPARAM))
  572. LRESULT WINAPI WndProc_Detour(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
  573. {
  574. if (OverlayWndProcHandler(hWnd, msg, wParam, lParam))
  575. return 1;
  576. return WndProc_Trampoline(hWnd, msg, wParam, lParam);
  577. }
  578. static IDirect3DDevice9* AcquireDevice()
  579. {
  580. IDirect3DDevice9* pDevice = nullptr;
  581. if (pGraphicsEngine && pGraphicsEngine->pRender && pGraphicsEngine->pRender->pD3DDevice)
  582. {
  583. return pGraphicsEngine->pRender->pD3DDevice;
  584. }
  585. return nullptr;
  586. }
  587. static bool InstallD3D9Hooks()
  588. {
  589. bool success = false;
  590. HMODULE hD3D9Module = nullptr;
  591. if (!hD3D9Module)
  592. {
  593. hD3D9Module = GetModuleHandle("d3d9.dll");
  594. }
  595. // Acquire the address of the virtual function table by creating an instance of IDirect3DDevice9Ex.
  596. if (hD3D9Module)
  597. {
  598. D3D9CREATEEXPROC d3d9CreateEx = (D3D9CREATEEXPROC)GetProcAddress(hD3D9Module, "Direct3DCreate9Ex");
  599. if (!d3d9CreateEx)
  600. {
  601. DebugSpewAlways("InstallD3D9Hooks: Failed to get address of Direct3DCreate9Ex");
  602. return false;
  603. }
  604. wil::com_ptr_nothrow<IDirect3D9Ex> d3d9ex;
  605. HRESULT hResult = (*d3d9CreateEx)(D3D_SDK_VERSION, &d3d9ex);
  606. if (!SUCCEEDED(hResult))
  607. {
  608. DebugSpewAlways("InstallD3D9Hooks: Call to Direct3DCreate9Ex failed. Result: %x", hResult);
  609. return false;
  610. }
  611. D3DPRESENT_PARAMETERS pp;
  612. ZeroMemory(&pp, sizeof(pp));
  613. pp.Windowed = 1;
  614. pp.SwapEffect = D3DSWAPEFFECT_FLIP;
  615. pp.BackBufferFormat = D3DFMT_A8R8G8B8;
  616. pp.BackBufferCount = 1;
  617. pp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
  618. // save the rounding state. We'll restore it after we're done here.
  619. // For some reason, CreateDeviceEx seems to tamper with it.
  620. int round = fegetround();
  621. wil::com_ptr_nothrow<IDirect3DDevice9> device;
  622. hResult = d3d9ex->CreateDeviceEx(
  623. D3DADAPTER_DEFAULT,
  624. D3DDEVTYPE_NULLREF,
  625. nullptr,
  626. D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_NOWINDOWCHANGES,
  627. &pp, nullptr, (IDirect3DDevice9Ex**)&device);
  628. if (!SUCCEEDED(hResult))
  629. {
  630. DebugSpewAlways("InstallD3D9Hooks: failed to CreateDeviceEx. Result: %x", hResult);
  631. device = AcquireDevice();
  632. }
  633. if (device)
  634. {
  635. success = true;
  636. // IDirect3DDevice9 virtual function hooks
  637. uintptr_t* d3dDevice_vftable = *(uintptr_t**)device.get();
  638. InstallDetour(d3dDevice_vftable[0x29], &RenderHooks::BeginScene_Detour, RenderHooks::BeginScene_Trampoline_Ptr, "d3dDevice_BeginScene");
  639. InstallDetour(d3dDevice_vftable[0x2a], &RenderHooks::EndScene_Detour, RenderHooks::EndScene_Trampoline_Ptr, "d3dDevice_EndScene");
  640. }
  641. // restore floating point rounding state
  642. fesetround(round);
  643. }
  644. return success;
  645. }
  646. enum class eOverlayHookStatus
  647. {
  648. Success,
  649. Failed,
  650. MissingDevice,
  651. };
  652. static eOverlayHookStatus InitializeOverlayHooks()
  653. {
  654. if (gbDeviceHooksInstalled)
  655. {
  656. if (!gpD3D9Device)
  657. {
  658. return eOverlayHookStatus::MissingDevice;
  659. }
  660. return eOverlayHookStatus::Success;
  661. }
  662. if (!pGraphicsEngine || !pGraphicsEngine->pRender || !pGraphicsEngine->pRender->pD3DDevice)
  663. {
  664. return eOverlayHookStatus::MissingDevice;
  665. }
  666. if (!InstallD3D9Hooks())
  667. {
  668. DebugSpewAlways("Failed to hook DirectX, We won't be able to render into the game!");
  669. return eOverlayHookStatus::Failed;
  670. }
  671. gbDeviceHooksInstalled = true;
  672. gLastGameState = gGameState;
  673. return !gpD3D9Device ? eOverlayHookStatus::MissingDevice : eOverlayHookStatus::Success;
  674. }
  675. //============================================================================
  676. class ImGuiRenderDebug
  677. {
  678. static inline D3DCAPS9 s_caps;
  679. struct RenderTargetInfo
  680. {
  681. wil::com_ptr_nothrow<IDirect3DTexture9> texture;
  682. void* surface = nullptr; // just the address of the target surface
  683. bool isBackBuffer = false;
  684. D3DSURFACE_DESC surfaceDesc;
  685. bool active = false;
  686. HRESULT status = S_OK;
  687. RenderTargetInfo()
  688. {
  689. }
  690. ~RenderTargetInfo()
  691. {
  692. }
  693. };
  694. std::vector<RenderTargetInfo> m_renderTargets;
  695. bool m_active = false;
  696. public:
  697. ImGuiRenderDebug()
  698. {
  699. }
  700. ~ImGuiRenderDebug()
  701. {
  702. m_renderTargets.clear();
  703. }
  704. void CreateObjects()
  705. {
  706. }
  707. void InvalidateObjects()
  708. {
  709. m_renderTargets.clear();
  710. }
  711. void Render()
  712. {
  713. }
  714. void NewFrame()
  715. {
  716. for (RenderTargetInfo& info : m_renderTargets)
  717. {
  718. info.active = false;
  719. }
  720. }
  721. void DrawSurfaceTexture(ImTextureID my_tex_id, float my_tex_w, float my_tex_h)
  722. {
  723. auto& io = ImGui::GetIO();
  724. ImVec2 pos = ImGui::GetCursorScreenPos();
  725. ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint
  726. ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); // 50% opaque white
  727. const float preview_w = std::min(my_tex_w, 256.f);
  728. const float preview_h = (preview_w / my_tex_w) * my_tex_h;
  729. ImGui::Image(my_tex_id, ImVec2(preview_w, preview_h), ImVec2(0, 0), ImVec2(1, 1), tint_col,
  730. border_col);
  731. float region_sz = 256.0f;
  732. if (ImGui::IsItemHovered()
  733. && my_tex_w >= region_sz
  734. && my_tex_h >= region_sz)
  735. {
  736. ImGui::BeginTooltip();
  737. float region_x = ((io.MousePos.x - pos.x) * (my_tex_w / preview_w)) - region_sz * 0.5f;
  738. float region_y = ((io.MousePos.y - pos.y) * (my_tex_h / preview_h)) - region_sz * 0.5f;
  739. if (region_x < 0.0f) { region_x = 0.0f; }
  740. else if (region_x > my_tex_w - region_sz) { region_x = my_tex_w - region_sz; }
  741. if (region_y < 0.0f) { region_y = 0.0f; }
  742. else if (region_y > my_tex_h - region_sz) { region_y = my_tex_h - region_sz; }
  743. ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y);
  744. ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz);
  745. ImVec2 uv0 = ImVec2((region_x + 0) / my_tex_w, (region_y + 0) / my_tex_h);
  746. ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h);
  747. float zoom = 1.0f;
  748. ImGui::Image(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, tint_col, border_col);
  749. ImGui::EndTooltip();
  750. }
  751. }
  752. void RenderImGui()
  753. {
  754. m_active = true;
  755. ImGui::Text("Num BeginScene Calls: %d", s_numBeginSceneCalls);
  756. auto& io = ImGui::GetIO();
  757. ImVec2 pos = ImGui::GetCursorScreenPos();
  758. for (RenderTargetInfo& info : m_renderTargets)
  759. {
  760. char szLabel[256];
  761. sprintf_s(szLabel, "%s: %p (%d x %d)", info.isBackBuffer ? "Back Buffer" : "Render Target", info.surface, info.surfaceDesc.Width, info.surfaceDesc.Height);
  762. if (!info.active)
  763. strcat_s(szLabel, " (Inactive)");
  764. char szId[256];
  765. sprintf_s(szId, "##%p", info.surface);
  766. strcat_s(szLabel, szId);
  767. if (ImGui::CollapsingHeader(szLabel))
  768. {
  769. ImGui::Text("Format: %d", info.surfaceDesc.Format);
  770. ImGui::Text("Type: %d", info.surfaceDesc.Type);
  771. ImGui::Text("Usage: %d", info.surfaceDesc.Usage);
  772. char poolName[32];
  773. switch (info.surfaceDesc.Pool)
  774. {
  775. case D3DPOOL_DEFAULT: strcpy_s(poolName, "D3DPOOL_DEFAULT"); break;
  776. case D3DPOOL_MANAGED: strcpy_s(poolName, "D3DPOOL_MANAGED"); break;
  777. case D3DPOOL_SYSTEMMEM: strcpy_s(poolName, "D3DPOOL_SYSTEMMEM"); break;
  778. case D3DPOOL_SCRATCH: strcpy_s(poolName, "D3DPOOL_SCRATCH"); break;
  779. default: sprintf_s(poolName, "Unknown (%d)", info.surfaceDesc.Pool); break;
  780. }
  781. ImGui::Text("Pool: %s", poolName);
  782. if (info.surfaceDesc.MultiSampleType)
  783. ImGui::Text("MultiSampleType: %d", info.surfaceDesc.MultiSampleType);
  784. if (info.surfaceDesc.MultiSampleQuality)
  785. ImGui::Text("MultiSampleQuality: %d", info.surfaceDesc.MultiSampleQuality);
  786. if (info.status != 0)
  787. ImGui::Text("Status: %x", info.status);
  788. if (info.texture)
  789. {
  790. DrawSurfaceTexture((ImTextureID)info.texture.get(), (float)info.surfaceDesc.Width, (float)info.surfaceDesc.Height);
  791. }
  792. }
  793. }
  794. if (ImGui::Button("Clear Inactive"))
  795. {
  796. m_renderTargets.erase(
  797. std::remove_if(m_renderTargets.begin(), m_renderTargets.end(),
  798. [](const auto& v) { return !v.active; }), m_renderTargets.end());
  799. }
  800. NewFrame();
  801. }
  802. RenderTargetInfo* GetRenderTargetInfo(IDirect3DSurface9* surface)
  803. {
  804. for (RenderTargetInfo& info : m_renderTargets)
  805. {
  806. if (info.surface == surface)
  807. return &info;
  808. }
  809. RenderTargetInfo info;
  810. info.surface = surface;
  811. surface->GetDesc(&info.surfaceDesc);
  812. m_renderTargets.push_back(info);
  813. return &m_renderTargets[m_renderTargets.size() - 1];
  814. }
  815. void UpdateSurface(const wil::com_ptr_nothrow<IDirect3DSurface9>& surface, bool backBuffer)
  816. {
  817. RenderTargetInfo* info = GetRenderTargetInfo(surface.get());
  818. info->isBackBuffer = backBuffer;
  819. if (info->active)
  820. return; // already processed this surface this frame
  821. info->active = true;
  822. HRESULT hr = S_OK;
  823. if (info->texture == nullptr)
  824. {
  825. hr = gpD3D9Device->CreateTexture(info->surfaceDesc.Width, info->surfaceDesc.Height, 1, D3DUSAGE_RENDERTARGET,
  826. info->surfaceDesc.Format, D3DPOOL_DEFAULT, &info->texture, 0);
  827. info->status = hr;
  828. if (hr != S_OK)
  829. return;
  830. }
  831. // should have texture now.
  832. wil::com_ptr_nothrow<IDirect3DSurface9> texSurface;
  833. hr = info->texture->GetSurfaceLevel(0, &texSurface);
  834. info->status = hr;
  835. if (hr != S_OK)
  836. return;
  837. hr = gpD3D9Device->StretchRect(surface.get(), nullptr, texSurface.get(), nullptr, D3DTEXF_LINEAR);
  838. info->status = hr;
  839. }
  840. void UpdateRenderTargets()
  841. {
  842. if (!m_active)
  843. return;
  844. gpD3D9Device->GetDeviceCaps(&s_caps);
  845. for (int i = 0; i < (int)s_caps.NumSimultaneousRTs; ++i)
  846. {
  847. wil::com_ptr_nothrow<IDirect3DSurface9> surface;
  848. HRESULT hr = gpD3D9Device->GetRenderTarget(i, &surface);
  849. if (hr == S_OK)
  850. {
  851. UpdateSurface(surface, false);
  852. }
  853. }
  854. UINT numSwapchains = gpD3D9Device->GetNumberOfSwapChains();
  855. for (UINT i = 0; i < numSwapchains; ++i)
  856. {
  857. wil::com_ptr_nothrow<IDirect3DSwapChain9> swapChain = nullptr;
  858. HRESULT hr = gpD3D9Device->GetSwapChain(i, &swapChain);
  859. if (hr == S_OK)
  860. {
  861. wil::com_ptr_nothrow<IDirect3DSurface9> surface;
  862. hr = swapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &surface);
  863. if (hr == S_OK)
  864. {
  865. UpdateSurface(surface, false);
  866. }
  867. }
  868. }
  869. wil::com_ptr_nothrow<IDirect3DSurface9> surface;
  870. HRESULT hr = gpD3D9Device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &surface);
  871. if (hr == S_OK)
  872. {
  873. UpdateSurface(surface, true);
  874. }
  875. }
  876. };
  877. static ImGuiRenderDebug* s_renderDebug = nullptr;
  878. static int s_renderCallbacksId = -1;
  879. void ImGuiRenderDebug_UpdateImGui()
  880. {
  881. if (s_renderDebug)
  882. s_renderDebug->RenderImGui();
  883. }
  884. void ImGuiRenderDebug_CreateObjects()
  885. {
  886. if (s_renderDebug)
  887. s_renderDebug->CreateObjects();
  888. }
  889. void ImGuiRenderDebug_InvalidateObjects()
  890. {
  891. if (s_renderDebug)
  892. s_renderDebug->InvalidateObjects();
  893. }
  894. void ImGuiRenderDebug_Render()
  895. {
  896. if (s_renderDebug)
  897. s_renderDebug->Render();
  898. }
  899. void ImGuiRenderDebug_UpdateRenderTargets()
  900. {
  901. if (s_renderDebug)
  902. s_renderDebug->UpdateRenderTargets();
  903. }
  904. //============================================================================
  905. class C2DPrimitiveManager_Hook
  906. {
  907. public:
  908. DETOUR_TRAMPOLINE_DEF(void, AddCachedText_Trampoline, (CTextObjectBase* obj))
  909. void AddCachedText_Detour(CTextObjectBase* obj)
  910. {
  911. if (this == nullptr)
  912. return;
  913. AddCachedText_Trampoline(obj);
  914. }
  915. };
  916. //============================================================================
  917. static void InitializeOverlayInternal();
  918. static void ShutdownOverlayInternal();
  919. static ImFontAtlas* s_fontAtlas = nullptr;
  920. void CreateImGuiContext()
  921. {
  922. if (s_fontAtlas == nullptr)
  923. {
  924. s_fontAtlas = new ImFontAtlas();
  925. ImGuiManager_BuildFonts(s_fontAtlas);
  926. }
  927. // Initialize ImGui context
  928. ImGui::CreateContext(s_fontAtlas);
  929. ImGuiIO& io = ImGui::GetIO();
  930. fmt::format_to(ImGuiSettingsFile, "{}/MacroQuest_Overlay.ini", mq::internal_paths::Config);
  931. if (s_deferredClearSettings)
  932. {
  933. std::error_code ec;
  934. if (std::filesystem::is_regular_file(ImGuiSettingsFile, ec))
  935. std::filesystem::remove(ImGuiSettingsFile, ec);
  936. }
  937. io.IniFilename = &ImGuiSettingsFile[0];
  938. ImGui::StyleColorsDark();
  939. mq::imgui::ConfigureStyle();
  940. }
  941. void DestroyImGuiContext()
  942. {
  943. ImGui::DestroyContext();
  944. delete s_fontAtlas;
  945. s_fontAtlas = nullptr;
  946. }
  947. void ReloadImGuiContext()
  948. {
  949. if (ImGui::GetCurrentContext() != nullptr)
  950. {
  951. ImGui::DestroyContext();
  952. }
  953. CreateImGuiContext();
  954. }
  955. static void OverlaySettings()
  956. {
  957. //if (ImGui::Checkbox("Enable Docking", &gbEnableImGuiDocking))
  958. //{
  959. // WritePrivateProfileBool("Overlay", "EnableDocking", gbEnableImGuiDocking, mq::internal_paths::MQini);
  960. // ResetOverlay();
  961. //}
  962. if (ImGui::Checkbox("Enable Viewports", &s_enableImGuiViewports))
  963. {
  964. WritePrivateProfileBool("Overlay", "EnableViewports", s_enableImGuiViewports, mq::internal_paths::MQini);
  965. ResetOverlay();
  966. }
  967. ImGui::SameLine();
  968. mq::imgui::HelpMarker("The viewports feature allows ImGui windows to be dragged out of the window into "
  969. "their own floating windows. This feature is BETA quality and has some known issues.\n"
  970. "\n"
  971. "Viewports are disabled when running in full screen mode.");
  972. ImGui::NewLine();
  973. if (ImGui::Button("Clear Saved ImGui Window Settings"))
  974. {
  975. s_deferredClearSettings = true;
  976. gbNeedResetOverlay = true;
  977. }
  978. }
  979. void InitializeMQ2Overlay()
  980. {
  981. if (gbOverlayInitialized)
  982. return;
  983. s_enableImGuiViewports = GetPrivateProfileBool("Overlay", "EnableViewports", false, mq::internal_paths::MQini);
  984. //gbEnableImGuiDocking = GetPrivateProfileBool("Overlay", "EnableDocking", true, mq::internal_paths::MQini);
  985. if (gbWriteAllConfig)
  986. {
  987. WritePrivateProfileBool("Overlay", "EnableViewports", s_enableImGuiViewports, mq::internal_paths::MQini);
  988. //WritePrivateProfileBool("Overlay", "EnableDocking", gbEnableImGuiDocking, mq::internal_paths::MQini);
  989. }
  990. // Intercept mouse events
  991. EzDetour(__ProcessMouseEvents, ProcessMouseEvents_Detour, ProcessMouseEvents_Trampoline);
  992. #if defined(__HandleMouseWheel_x)
  993. // Intercept mouse wheel events
  994. EzDetour(__HandleMouseWheel, HandleMouseWheel_Detour, HandleMouseWheel_Trampoline);
  995. #endif
  996. // Intercept keyboard events
  997. EzDetour(__ProcessKeyboardEvents, ProcessKeyboardEvents_Detour, ProcessKeyboardEvents_Trampoline);
  998. // Hook the main window proc.
  999. EzDetour(__WndProc, WndProc_Detour, WndProc_Trampoline);
  1000. // Hook particle render function
  1001. EzDetour(CParticleSystem__Render, &CParticleSystemHook::Render_Detour, &CParticleSystemHook::Render_Trampoline);
  1002. // Hook the reset device function
  1003. EzDetour(CRender__ResetDevice, &CRenderHook::ResetDevice_Detour, &CRenderHook::ResetDevice_Trampoline);
  1004. EzDetour(C2DPrimitiveManager__AddCachedText, &C2DPrimitiveManager_Hook::AddCachedText_Detour, &C2DPrimitiveManager_Hook::AddCachedText_Trampoline);
  1005. CreateImGuiContext();
  1006. InitializeOverlayInternal();
  1007. s_renderDebug = new ImGuiRenderDebug();
  1008. s_renderCallbacksId = AddRenderCallbacks(
  1009. { ImGuiRenderDebug_CreateObjects, ImGuiRenderDebug_InvalidateObjects, ImGuiRenderDebug_Render });
  1010. AddSettingsPanel("Overlay", OverlaySettings);
  1011. gbOverlayInitialized = true;
  1012. }
  1013. void InitializeOverlayInternal()
  1014. {
  1015. auto status = InitializeOverlayHooks();
  1016. if (status != eOverlayHookStatus::Success)
  1017. {
  1018. gbRetryHooks = (status == eOverlayHookStatus::MissingDevice);
  1019. gbInitializationFailed = (status == eOverlayHookStatus::Failed);
  1020. }
  1021. }
  1022. void ShutdownMQ2Overlay()
  1023. {
  1024. if (!gbOverlayInitialized)
  1025. return;
  1026. RemoveSettingsPanel("Overlay");
  1027. RemoveRenderCallbacks(s_renderCallbacksId);
  1028. s_renderCallbacksId = -1;
  1029. delete s_renderDebug; s_renderDebug = nullptr;
  1030. RemoveDetour(__ProcessMouseEvents);
  1031. #if defined(__HandleMouseWheel_x)
  1032. RemoveDetour(__HandleMouseWheel);
  1033. #endif
  1034. RemoveDetour(__ProcessKeyboardEvents);
  1035. RemoveDetour(__WndProc);
  1036. RemoveDetour(CParticleSystem__Render);
  1037. RemoveDetour(CRender__ResetDevice);
  1038. RemoveDetour(C2DPrimitiveManager__AddCachedText);
  1039. ShutdownOverlayInternal();
  1040. DestroyImGuiContext();
  1041. gbOverlayInitialized = false;
  1042. }
  1043. void ShutdownOverlayInternal()
  1044. {
  1045. if (!gbDeviceHooksInstalled)
  1046. return;
  1047. RemoveDetours();
  1048. gbDeviceHooksInstalled = false;
  1049. gHooks.clear();
  1050. ShutdownImGui();
  1051. gpD3D9Device = nullptr;
  1052. gResetDeviceAddress = 0;
  1053. gMouseLocation = POINT{};
  1054. gbMouseBlocked = false;
  1055. gbFlushNextMouse = false;
  1056. gbRetryHooks = false;
  1057. gbInitializationFailed = false;
  1058. gbDeviceAcquired = false;
  1059. }
  1060. void DoResetOverlay()
  1061. {
  1062. if (!gbDeviceHooksInstalled)
  1063. return;
  1064. SPDLOG_INFO("MQ2Overlay: Resetting overlay");
  1065. if (gbDeviceAcquired)
  1066. {
  1067. Renderer_InvalidateDeviceObjects();
  1068. gbDeviceAcquired = false;
  1069. }
  1070. ShutdownImGui();
  1071. ReloadImGuiContext();
  1072. gpD3D9Device = nullptr;
  1073. gResetDeviceAddress = 0;
  1074. }
  1075. // Exported - should defer to next pulse
  1076. void ResetOverlay()
  1077. {
  1078. gbNeedResetOverlay = true;
  1079. }
  1080. void PulseMQ2Overlay()
  1081. {
  1082. if (!gbOverlayInitialized)
  1083. return;
  1084. s_numBeginSceneCalls = 0;
  1085. // Reset the device hooks between game states. Some of them may alter
  1086. // the device and we might need to start over.
  1087. if (gGameState != gLastGameState)
  1088. {
  1089. DebugSpewAlways("Game State Changed: %d, resetting device", gGameState);
  1090. gLastGameState = gGameState;
  1091. DoResetOverlay();
  1092. }
  1093. else if (gbNeedResetOverlay)
  1094. {
  1095. DoResetOverlay();
  1096. gbNeedResetOverlay = false;
  1097. gReInitFrameDelay = 1;
  1098. }
  1099. if (gbRetryHooks)
  1100. {
  1101. gbRetryHooks = false;
  1102. InitializeOverlayInternal();
  1103. }
  1104. }
  1105. } // namespace mq