graphics_hook_lifecycle.cpp 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. #include "graphics_hook_lifecycle.h"
  2. #include <algorithm>
  3. #include "../../../core/ui/runtime/ui_visibility_runtime.h"
  4. #include "graphics_hook_replacements.h"
  5. namespace sunrise::client::hooks::graphics {
  6. SRWLOCK g_hookLock{SRWLOCK_INIT};
  7. std::atomic_uint g_activeHookCalls{};
  8. thread_local std::size_t g_hookCallDepth{};
  9. bool g_acceptRendererCalls{};
  10. std::array<hooking::detour::Handle, kHandleCount> g_handles{};
  11. std::array<void*, kHandleCount> g_targetEntries{};
  12. Targets g_targets{};
  13. namespace {
  14. /** @return True when every required graphics handle is attached. */
  15. [[nodiscard]] bool all_installed() noexcept {
  16. return std::all_of(g_handles.begin(),
  17. g_handles.end(),
  18. [](const hooking::detour::Handle& handle) { return handle.attached; });
  19. }
  20. /** @return True when at least one graphics handle is attached. */
  21. [[nodiscard]] bool any_installed() noexcept {
  22. return std::any_of(g_handles.begin(),
  23. g_handles.end(),
  24. [](const hooking::detour::Handle& handle) { return handle.attached; });
  25. }
  26. } // namespace
  27. /** Finds and installs both system DXGI hooks in one transaction. */
  28. bool install() noexcept {
  29. const core::ui::runtime::VisibilitySnapshot visibility = core::ui::runtime::snapshot();
  30. if (!visibility.initialized || !visibility.enabled) {
  31. return visibility.initialized;
  32. }
  33. AcquireSRWLockExclusive(&g_hookLock);
  34. if (all_installed()) {
  35. ReleaseSRWLockExclusive(&g_hookLock);
  36. return true;
  37. }
  38. if (!hook_calls_idle() || g_acceptRendererCalls || any_installed()
  39. || g_targets.present != nullptr || !discovery::resolve(g_targets)) {
  40. ReleaseSRWLockExclusive(&g_hookLock);
  41. return false;
  42. }
  43. const replacement::EntryPoints entryPoints = replacement::entry_points();
  44. const std::array specs{
  45. hooking::detour::Spec{g_targets.present,
  46. entryPoints[static_cast<std::size_t>(HookSlot::present)]},
  47. hooking::detour::Spec{g_targets.resizeBuffers,
  48. entryPoints[static_cast<std::size_t>(HookSlot::resizeBuffers)]},
  49. hooking::detour::Spec{g_targets.setFullscreenState,
  50. entryPoints[static_cast<std::size_t>(HookSlot::setFullscreenState)]},
  51. };
  52. for (std::size_t index = 0; index < specs.size(); ++index) {
  53. g_targetEntries[index] = specs[index].target;
  54. }
  55. const bool installed = hooking::detour::install(specs, g_handles);
  56. if (installed) {
  57. g_acceptRendererCalls = true;
  58. } else {
  59. g_targetEntries = {};
  60. discovery::release(g_targets);
  61. }
  62. ReleaseSRWLockExclusive(&g_hookLock);
  63. return installed;
  64. }
  65. /** Detaches system hooks before releasing renderer and discovery resources. */
  66. bool uninstall() noexcept {
  67. if (current_thread_in_hook_call()) {
  68. // SRW locks cannot be upgraded recursively; the caller retries after the SDK call unwinds.
  69. return false;
  70. }
  71. AcquireSRWLockExclusive(&g_hookLock);
  72. if (any_installed() && !all_installed()) {
  73. ReleaseSRWLockExclusive(&g_hookLock);
  74. renderer::dispatch_pending_input_release();
  75. return false;
  76. }
  77. if (!any_installed()) {
  78. if (!hook_calls_idle() || !renderer::shutdown()) {
  79. ReleaseSRWLockExclusive(&g_hookLock);
  80. renderer::dispatch_pending_input_release();
  81. return false;
  82. }
  83. } else {
  84. g_acceptRendererCalls = false;
  85. if (!renderer::shutdown()) {
  86. // The attached Present stays available to send an owner-thread capture release.
  87. g_acceptRendererCalls = true;
  88. ReleaseSRWLockExclusive(&g_hookLock);
  89. renderer::dispatch_pending_input_release();
  90. return false;
  91. }
  92. const replacement::ProtectedEntries protectedEntries = replacement::protected_entries();
  93. const hooking::detour::UninstallResult removal =
  94. hooking::detour::uninstall(g_handles, protectedEntries);
  95. if (removal == hooking::detour::UninstallResult::failed) {
  96. // A failed transaction keeps the handles attached and the original targets callable.
  97. ReleaseSRWLockExclusive(&g_hookLock);
  98. renderer::dispatch_pending_input_release();
  99. return false;
  100. }
  101. if (removal == hooking::detour::UninstallResult::protectedCodeActive) {
  102. // Suspended ingress keeps entry closed until a later detach retry.
  103. ReleaseSRWLockExclusive(&g_hookLock);
  104. renderer::dispatch_pending_input_release();
  105. return false;
  106. }
  107. if (!hook_calls_idle()) {
  108. // Detached replacements keep the restored targets until the retry drains.
  109. ReleaseSRWLockExclusive(&g_hookLock);
  110. renderer::dispatch_pending_input_release();
  111. return false;
  112. }
  113. }
  114. if (g_acceptRendererCalls) {
  115. // A detached state must never publish renderer work into a later restart.
  116. g_acceptRendererCalls = false;
  117. }
  118. if (!hook_calls_idle()) {
  119. ReleaseSRWLockExclusive(&g_hookLock);
  120. renderer::dispatch_pending_input_release();
  121. return false;
  122. }
  123. g_targetEntries = {};
  124. discovery::release(g_targets);
  125. ReleaseSRWLockExclusive(&g_hookLock);
  126. renderer::dispatch_pending_input_release();
  127. return true;
  128. }
  129. /** @return True only when both system DXGI method hooks are attached. */
  130. bool is_installed() noexcept {
  131. AcquireSRWLockShared(&g_hookLock);
  132. const bool installed = all_installed();
  133. ReleaseSRWLockShared(&g_hookLock);
  134. return installed;
  135. }
  136. } // namespace sunrise::client::hooks::graphics