steam_lifecycle.cpp 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #include <Windows.h>
  2. #include <atomic>
  3. #include "../../client/graphics/wine_compat.h"
  4. #include "../../client/hooks/egress/runtime.h"
  5. #include "../../client/hooks/package_trust/package_trust_bypass.h"
  6. #include "../../client/runtime/runtime.h"
  7. #include "../../core/logging/log.h"
  8. #include "../../core/runtime/core_runtime.h"
  9. #include "../../core/runtime/host_environment.h"
  10. #include "callbacks/callback_registry.h"
  11. #include "core/threading/data_mutex.h"
  12. #include "internal.h"
  13. #include "runtime.h"
  14. #include "steam_context_state.h"
  15. namespace sunrise::steam {
  16. namespace {
  17. /** The only delay-loaded module allowed to start the platform Client group. */
  18. constexpr wchar_t kNetworkingModuleName[] = L"steamnetworkingsockets.dll";
  19. struct Lifecycle {
  20. bool mainActivationDone{};
  21. bool mainActivationResult{};
  22. bool graphicsActivationAttempted{};
  23. bool platformActivationAttempted{};
  24. };
  25. core::threading::SharedDataMutex<Lifecycle> g_lifecycle;
  26. std::atomic_bool g_initialized{false};
  27. /**
  28. * Finds the loaded image that owns a caught return address. It takes no module reference.
  29. * @return The owning module, or null when it is not the Steam networking image.
  30. */
  31. [[nodiscard]] HMODULE networking_caller_module(const void* callerAddress) noexcept {
  32. if (callerAddress == nullptr) {
  33. return nullptr;
  34. }
  35. HMODULE callerModule{};
  36. const DWORD flags =
  37. GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
  38. if (GetModuleHandleExW(flags, reinterpret_cast<LPCWSTR>(callerAddress), &callerModule) == FALSE
  39. || callerModule != GetModuleHandleW(kNetworkingModuleName)) {
  40. return nullptr;
  41. }
  42. return callerModule;
  43. }
  44. } // namespace
  45. /** Starts the Steam shim and its exported state. */
  46. bool initialize(void* module) noexcept {
  47. if (core::runtime::is_wine()) {
  48. client::graphics::initialize_wine_display();
  49. }
  50. if (!client::hooks::egress::install()) {
  51. return false;
  52. }
  53. return g_lifecycle.lock_write([module](Lifecycle& lifecycle) {
  54. if (g_initialized.load(std::memory_order_acquire)) {
  55. return true;
  56. }
  57. if (!core::initialize(module)) {
  58. return false;
  59. }
  60. // Base generation (_0) packages register during bootload, before the first callback pump,
  61. // so package trust must attach at Steam init rather than in the main-image hook sweep.
  62. if (!client::hooks::package_trust::install()) {
  63. core::log::write(core::log::Channel::client,
  64. core::log::Level::error,
  65. "ev=steam_init stage=package_trust result=fail");
  66. (void)core::shutdown();
  67. return false;
  68. }
  69. advance_context_generation();
  70. g_initialized.store(true, std::memory_order_release);
  71. core::log::write(
  72. core::log::Channel::client, core::log::Level::info, "ev=steam_init result=ok");
  73. // The guard attaches above, before Core logging exists, so its outcome is reported here.
  74. client::hooks::egress::report_installation();
  75. return true;
  76. });
  77. }
  78. /** Stops callback delivery and clears Steam state. */
  79. bool shutdown() noexcept {
  80. return g_lifecycle.lock_write([](Lifecycle& lifecycle) {
  81. const bool hadRuntime =
  82. g_initialized.load(std::memory_order_acquire) || core::is_initialized();
  83. if (!hadRuntime) {
  84. return true;
  85. }
  86. if (!core::shutdown()) {
  87. core::log::write(core::log::Channel::client,
  88. core::log::Level::error,
  89. "ev=steam_shutdown stage=core result=fail");
  90. return false;
  91. }
  92. // Callback pointers are released only after Client hooks stop producing events.
  93. runtime::callbacks::clear();
  94. g_initialized.store(false, std::memory_order_release);
  95. lifecycle.mainActivationDone = false;
  96. lifecycle.mainActivationResult = false;
  97. lifecycle.graphicsActivationAttempted = false;
  98. lifecycle.platformActivationAttempted = false;
  99. advance_context_generation();
  100. return true;
  101. });
  102. }
  103. /** @return True. The in-process Steam provider stays up for the whole DLL lifetime. */
  104. bool is_running() noexcept {
  105. return true;
  106. }
  107. } // namespace sunrise::steam
  108. namespace sunrise::steam::runtime {
  109. /** Runs main-image activation once, from a caller that proves the game is loaded. */
  110. bool activate_main_once() noexcept {
  111. return g_lifecycle.lock_write([](Lifecycle& lifecycle) {
  112. if (!lifecycle.mainActivationDone && core::is_initialized()) {
  113. lifecycle.mainActivationDone = true;
  114. lifecycle.mainActivationResult = client::activate_main_once();
  115. }
  116. const bool result = lifecycle.mainActivationResult;
  117. return result;
  118. });
  119. }
  120. /** @return True while the main-image sweep has not run yet. */
  121. bool main_activation_pending() noexcept {
  122. const bool pending = g_lifecycle.lock_read(
  123. [](const Lifecycle& lifecycle) { return !lifecycle.mainActivationDone; });
  124. // Matches the activation's Core test. A failed Core must not raise an endless overlay.
  125. return pending && core::is_initialized();
  126. }
  127. /** Installs the presentation hooks once, from the callback pump, before the game sweep. */
  128. void activate_graphics_once() noexcept {
  129. g_lifecycle.lock_write([](Lifecycle& lifecycle) {
  130. if (!lifecycle.graphicsActivationAttempted && core::is_initialized()) {
  131. lifecycle.graphicsActivationAttempted = true;
  132. (void)client::activate_graphics_once();
  133. }
  134. });
  135. }
  136. /** Activates the platform Client group at its exact interface request boundary. */
  137. void activate_platform_once(const void* callerAddress) noexcept {
  138. const HMODULE callerModule = networking_caller_module(callerAddress);
  139. if (callerModule == nullptr) {
  140. return;
  141. }
  142. g_lifecycle.lock_write([callerModule](Lifecycle& lifecycle) {
  143. if (!lifecycle.platformActivationAttempted
  144. && g_initialized.load(std::memory_order_acquire)) {
  145. lifecycle.platformActivationAttempted = true;
  146. (void)client::activate_platform_once(callerModule);
  147. }
  148. });
  149. }
  150. } // namespace sunrise::steam::runtime