client_hook_activation.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. #include <Windows.h>
  2. #include <array>
  3. #include <cstdio>
  4. #include <span>
  5. #include <string_view>
  6. #include "../../core/logging/log.h"
  7. #include "../../core/ui/busy/busy.h"
  8. #include "../../core/ui/notice/ui_notice_overlay.h"
  9. #include "../content/bootstrap/bootstrap_token_publish.h"
  10. #include "../content/investment/worker.h"
  11. #include "../executable/image.h"
  12. #include "../hooks/assert_handler/assert_handler_lifecycle.h"
  13. #include "../hooks/bitmap/bitmap_hook_lifecycle.h"
  14. #include "../hooks/bootflow/bootflow_hook_lifecycle.h"
  15. #include "../hooks/config_getter/config_getter_lifecycle.h"
  16. #include "../hooks/cursor/runtime.h"
  17. #include "../hooks/graphics/graphics_hook_lifecycle.h"
  18. #include "../hooks/network/runtime.h"
  19. #include "../hooks/polled_input/runtime.h"
  20. #include "../hooks/queuez/queuez_hook_lifecycle.h"
  21. #include "../hooks/retail_log/retail_log_lifecycle.h"
  22. #include "../hooks/teleport/runtime.h"
  23. #include "../patterns/registry.h"
  24. #include "../targets/game.h"
  25. #include "internal.h"
  26. #include "runtime.h"
  27. namespace sunrise::client::runtime {
  28. SRWLOCK g_lock{SRWLOCK_INIT};
  29. StageState g_mainStage{StageState::pending};
  30. StageState g_graphicsStage{StageState::pending};
  31. StageState g_platformStage{StageState::pending};
  32. HMODULE g_platformModule{};
  33. namespace {
  34. /** Main-image executable ranges remain valid while the process is loaded. */
  35. struct GameImageRanges {
  36. executable::ExecutableImage executable;
  37. std::array<patterns::ImageRange, executable::kPeSectionLimit> ranges{};
  38. };
  39. /**
  40. * Inspects the main image and maps its executable sections to scanner ranges.
  41. * @param output Receives the inspected image and matching scanner ranges.
  42. * @return True when the main PE image has at least one valid executable range.
  43. */
  44. [[nodiscard]] bool inspect_game_image(GameImageRanges& output) noexcept {
  45. output = {};
  46. if (!executable::inspect_main_module(output.executable)) {
  47. return false;
  48. }
  49. for (std::size_t index = 0; index < output.executable.count; ++index) {
  50. output.ranges[index] = patterns::ImageRange{output.executable.sections[index]};
  51. }
  52. return true;
  53. }
  54. /** @param image Inspected main image. @return Populated executable scanner ranges. */
  55. [[nodiscard]] std::span<patterns::ImageRange> ranges(GameImageRanges& image) noexcept {
  56. return std::span(image.ranges.data(), image.executable.count);
  57. }
  58. /** Reports which resolve stage rejected the sweep, naming a missed signature. */
  59. void report_resolve_failure() noexcept {
  60. const auto failure = targets::game::resolution::last_failure();
  61. if (failure == targets::game::resolution::Failure::networkDerive) {
  62. core::log::write(core::log::Channel::client,
  63. core::log::Level::error,
  64. "ev=activate stage=game_targets reason=network_derive result=fail");
  65. return;
  66. }
  67. if (failure == targets::game::resolution::Failure::contentDerive) {
  68. core::log::write(core::log::Channel::client,
  69. core::log::Level::error,
  70. "ev=activate stage=game_targets reason=content_derive result=fail");
  71. return;
  72. }
  73. const std::string_view name = targets::game::resolution::last_failed_signature();
  74. std::array<char, 128> line{};
  75. const int written = std::snprintf(line.data(),
  76. line.size(),
  77. "ev=activate stage=game_targets reason=signature name=%.*s "
  78. "result=fail",
  79. static_cast<int>(name.size()),
  80. name.data());
  81. if (written <= 0) {
  82. core::log::write(core::log::Channel::client,
  83. core::log::Level::error,
  84. "ev=activate stage=game_targets reason=signature result=fail");
  85. return;
  86. }
  87. const auto length = static_cast<std::size_t>(written) < line.size()
  88. ? static_cast<std::size_t>(written)
  89. : line.size() - 1;
  90. core::log::write(
  91. core::log::Channel::client, core::log::Level::error, std::string_view(line.data(), length));
  92. }
  93. /** Clears both main-image target groups while no game hook owns their entries. */
  94. void clear_game_targets() noexcept {
  95. targets::game::content::clear();
  96. targets::game::network::clear();
  97. }
  98. /**
  99. * Resolves both main-image target groups from one inspection, then installs game hooks.
  100. * @return True when every required main-image target and game hook is ready.
  101. */
  102. [[nodiscard]] bool activate_required_main_locked() noexcept {
  103. GameImageRanges gameImage;
  104. if (!inspect_game_image(gameImage)) {
  105. core::log::write(core::log::Channel::client,
  106. core::log::Level::error,
  107. "ev=activate stage=game_image result=fail");
  108. clear_game_targets();
  109. return false;
  110. }
  111. const std::span<patterns::ImageRange> imageRanges = ranges(gameImage);
  112. if (!targets::game::resolution::resolve(imageRanges)) {
  113. report_resolve_failure();
  114. return false;
  115. }
  116. // The SignOn config blob carries this token. It must reach State before any hook owns the
  117. // resolved targets: extraction cannot recover from a missing bootstrap token.
  118. if (!content::bootstrap::publish_token()) {
  119. clear_game_targets();
  120. return false;
  121. }
  122. if (!hooks::network::install_game()) {
  123. core::log::write(core::log::Channel::client,
  124. core::log::Level::error,
  125. "ev=activate stage=game_network result=fail");
  126. if (!hooks::network::has_game_ownership()) {
  127. clear_game_targets();
  128. }
  129. return false;
  130. }
  131. core::log::write(core::log::Channel::client,
  132. core::log::Level::info,
  133. "ev=activate stage=game_network result=ok");
  134. const bool packageKeys = targets::game::packages::is_resolved();
  135. core::log::write(core::log::Channel::client,
  136. packageKeys ? core::log::Level::info : core::log::Level::warn,
  137. packageKeys ? "ev=activate stage=package_keys result=ok"
  138. : "ev=activate stage=package_keys result=fail");
  139. // Diagnostic capture reports its own outcome and never demotes this stage.
  140. (void)hooks::retail_log::install();
  141. (void)hooks::assert_handler::install();
  142. (void)hooks::config_getter::install();
  143. // Boot-step fixes scan for their own single-site targets; each reports its own outcome.
  144. (void)hooks::bootflow::install();
  145. // The teleport hooks attach whether or not the feature is on, so the interface can enable it
  146. // without a restart. Both replacements return immediately while nothing is requested.
  147. (void)hooks::teleport::install();
  148. (void)hooks::queuez::install();
  149. // The bitmap reference guard puts the none sentinel in place of a reference outside tag
  150. // space. Without it the widget's stored-reference reader faults.
  151. (void)hooks::bitmap::install();
  152. content::investment::worker::activate();
  153. return true;
  154. }
  155. } // namespace
  156. } // namespace sunrise::client::runtime
  157. namespace sunrise::client {
  158. /** Resolves main-image targets and installs required game hooks once. */
  159. bool activate_main_once() noexcept {
  160. AcquireSRWLockExclusive(&runtime::g_lock);
  161. if (runtime::g_mainStage != runtime::StageState::pending) {
  162. const bool active = runtime::g_mainStage == runtime::StageState::active;
  163. ReleaseSRWLockExclusive(&runtime::g_lock);
  164. return active;
  165. }
  166. // The sweep stalls whichever thread calls it, so the overlay says what is happening. It
  167. // only reaches the screen once the presentation hooks are installed.
  168. core::ui::busy::begin(core::ui::busy::Task::initialization);
  169. const bool active = runtime::activate_required_main_locked();
  170. core::ui::busy::end(core::ui::busy::Task::initialization);
  171. if (!active) {
  172. // A failed sweep latches too: repeating it stalls the frame loop for nothing.
  173. runtime::g_mainStage = runtime::StageState::failed;
  174. core::log::write(core::log::Channel::client,
  175. core::log::Level::error,
  176. "ev=activate stage=main result=fail");
  177. // The boot cannot reach orbit after this, so the user is told rather than left waiting.
  178. core::ui::notice::raise("Sunrise could not attach to the game. The boot will not finish.");
  179. ReleaseSRWLockExclusive(&runtime::g_lock);
  180. return false;
  181. }
  182. runtime::g_mainStage = runtime::StageState::active;
  183. core::log::write(
  184. core::log::Channel::client, core::log::Level::info, "ev=activate stage=main result=ok");
  185. ReleaseSRWLockExclusive(&runtime::g_lock);
  186. return true;
  187. }
  188. /** Installs the presentation hooks once, independently of the game image sweep. */
  189. bool activate_graphics_once() noexcept {
  190. AcquireSRWLockExclusive(&runtime::g_lock);
  191. if (runtime::g_graphicsStage != runtime::StageState::pending) {
  192. const bool active = runtime::g_graphicsStage == runtime::StageState::active;
  193. ReleaseSRWLockExclusive(&runtime::g_lock);
  194. return active;
  195. }
  196. if (!hooks::graphics::install()) {
  197. runtime::g_graphicsStage = runtime::StageState::failed;
  198. core::log::write(core::log::Channel::client,
  199. core::log::Level::error,
  200. "ev=activate stage=graphics_hooks result=fail");
  201. ReleaseSRWLockExclusive(&runtime::g_lock);
  202. return false;
  203. }
  204. runtime::g_graphicsStage = runtime::StageState::active;
  205. // The cursor guards only matter once the interface can be shown. A miss must not demote
  206. // presentation readiness, so it is logged and not propagated.
  207. (void)hooks::cursor::install();
  208. // The game reads its action keys by scanning GetKeyState every frame, which no window
  209. // procedure sees, so the polled guards carry the same terms as the cursor guards.
  210. (void)hooks::polled_input::install();
  211. core::log::write(
  212. core::log::Channel::client, core::log::Level::info, "ev=activate stage=graphics result=ok");
  213. ReleaseSRWLockExclusive(&runtime::g_lock);
  214. return true;
  215. }
  216. } // namespace sunrise::client