client_hook_activation.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. #include <Windows.h>
  2. #include <array>
  3. #include <cstdint>
  4. #include <cstdio>
  5. #include <span>
  6. #include <string_view>
  7. #include "../../core/logging/log.h"
  8. #include "../../core/settings/settings.h"
  9. #include "../../core/ui/busy/busy.h"
  10. #include "../../core/ui/notice/ui_notice_overlay.h"
  11. #include "../content/activity/scriptable_catalog_worker.h"
  12. #include "../content/bootstrap/bootstrap_token_publish.h"
  13. #include "../content/investment/worker.h"
  14. #include "../diagnostics/entity_create_probe.h"
  15. #include "../diagnostics/image_dump.h"
  16. #include "../executable/image.h"
  17. #include "../hooks/assert_handler/assert_handler_lifecycle.h"
  18. #include "../hooks/async_io/async_io_lifetime_guard.h"
  19. #include "../hooks/bitmap/bitmap_hook_lifecycle.h"
  20. #include "../hooks/bootflow/bootflow_hook_lifecycle.h"
  21. #include "../hooks/cine_auth_probe/cine_auth_probe.h"
  22. #include "../hooks/cine_probe/cine_probe.h"
  23. #include "../hooks/config_getter/config_getter_lifecycle.h"
  24. #include "../hooks/cursor/runtime.h"
  25. #include "../hooks/graphics/graphics_hook_lifecycle.h"
  26. #include "../hooks/hitch_probe/hitch_probe.h"
  27. #include "../hooks/inactivity/inactivity_override.h"
  28. #include "../hooks/infinite_ammo/infinite_ammo.h"
  29. #include "../hooks/membership_probe/membership_probe.h"
  30. #include "../hooks/network/runtime.h"
  31. #include "../hooks/noclip/runtime.h"
  32. #include "../hooks/package_trust/package_trust_bypass.h"
  33. #include "../hooks/peer_relay/peer_relay_direct.h"
  34. #include "../hooks/polled_input/runtime.h"
  35. #include "../hooks/queuez/queuez_hook_lifecycle.h"
  36. #include "../hooks/retail_log/retail_log_lifecycle.h"
  37. #include "../hooks/sense_chain_guard/sense_chain_guard.h"
  38. #include "../hooks/stall_probe/stall_probe.h"
  39. #include "../hooks/teleport/runtime.h"
  40. #include "../hooks/vendor_banner/vendor_banner_retire.h"
  41. #include "../hooks/world_objects/world_object_registry.h"
  42. #include "../patterns/registry.h"
  43. #include "../targets/game.h"
  44. #include "internal.h"
  45. #include "runtime.h"
  46. namespace sunrise::client::runtime {
  47. SRWLOCK g_lock{SRWLOCK_INIT};
  48. StageState g_mainStage{StageState::pending};
  49. StageState g_graphicsStage{StageState::pending};
  50. StageState g_platformStage{StageState::pending};
  51. HMODULE g_platformModule{};
  52. void* g_sunriseModule{};
  53. namespace {
  54. /** Main-image executable ranges remain valid while the process is loaded. */
  55. struct GameImageRanges {
  56. executable::ExecutableImage executable;
  57. std::array<patterns::ImageRange, executable::kPeSectionLimit> ranges{};
  58. };
  59. /**
  60. * Inspects the main image and maps its executable sections to scanner ranges.
  61. * @param output Receives the inspected image and matching scanner ranges.
  62. * @return True when the main PE image has at least one valid executable range.
  63. */
  64. [[nodiscard]] bool inspect_game_image(GameImageRanges& output) noexcept {
  65. output = {};
  66. if (!executable::inspect_main_module(output.executable)) {
  67. return false;
  68. }
  69. for (std::size_t index = 0; index < output.executable.count; ++index) {
  70. output.ranges[index] = patterns::ImageRange{output.executable.sections[index]};
  71. }
  72. return true;
  73. }
  74. /** @param image Inspected main image. @return Populated executable scanner ranges. */
  75. [[nodiscard]] std::span<patterns::ImageRange> ranges(GameImageRanges& image) noexcept {
  76. return std::span(image.ranges.data(), image.executable.count);
  77. }
  78. /** Reports which resolve stage rejected the sweep, naming a missed signature. */
  79. void report_resolve_failure() noexcept {
  80. const auto failure = targets::game::resolution::last_failure();
  81. if (failure == targets::game::resolution::Failure::networkDerive) {
  82. core::log::write(core::log::Channel::client,
  83. core::log::Level::error,
  84. "ev=activate stage=game_targets reason=network_derive result=fail");
  85. return;
  86. }
  87. if (failure == targets::game::resolution::Failure::contentDerive) {
  88. core::log::write(core::log::Channel::client,
  89. core::log::Level::error,
  90. "ev=activate stage=game_targets reason=content_derive result=fail");
  91. return;
  92. }
  93. const std::string_view name = targets::game::resolution::last_failed_signature();
  94. std::array<char, 128> line{};
  95. const int written = std::snprintf(line.data(),
  96. line.size(),
  97. "ev=activate stage=game_targets reason=signature name=%.*s "
  98. "result=fail",
  99. static_cast<int>(name.size()),
  100. name.data());
  101. if (written <= 0) {
  102. core::log::write(core::log::Channel::client,
  103. core::log::Level::error,
  104. "ev=activate stage=game_targets reason=signature result=fail");
  105. return;
  106. }
  107. const auto length = static_cast<std::size_t>(written) < line.size()
  108. ? static_cast<std::size_t>(written)
  109. : line.size() - 1;
  110. core::log::write(
  111. core::log::Channel::client, core::log::Level::error, std::string_view(line.data(), length));
  112. }
  113. /** Clears both main-image target groups while no game hook owns their entries. */
  114. void clear_game_targets() noexcept {
  115. targets::game::content::clear();
  116. targets::game::network::clear();
  117. }
  118. /**
  119. * Resolves both main-image target groups from one inspection, then installs game hooks.
  120. * @return True when every required main-image target and game hook is ready.
  121. */
  122. [[nodiscard]] bool activate_required_main_locked() noexcept {
  123. GameImageRanges gameImage;
  124. if (!inspect_game_image(gameImage)) {
  125. core::log::write(core::log::Channel::client,
  126. core::log::Level::error,
  127. "ev=activate stage=game_image result=fail");
  128. clear_game_targets();
  129. return false;
  130. }
  131. // The inspection above proves the packer has finished: these spans are the decrypted code the
  132. // signatures match. That makes this the first point at which a dump is worth taking.
  133. if (core::settings::get().client.dumpGameImage) {
  134. (void)diagnostics::dump_game_image(g_sunriseModule);
  135. }
  136. const std::span<patterns::ImageRange> imageRanges = ranges(gameImage);
  137. if (!targets::game::resolution::resolve(imageRanges)) {
  138. report_resolve_failure();
  139. return false;
  140. }
  141. // Steam initialization installs package trust before base-package registration. Keep this
  142. // idempotent check beside the other main-image hooks so activation also verifies ownership.
  143. if (!hooks::package_trust::install()) {
  144. clear_game_targets();
  145. return false;
  146. }
  147. // The SignOn config blob carries this token. It must reach State before any hook owns the
  148. // resolved targets: extraction cannot recover from a missing bootstrap token.
  149. if (!content::bootstrap::publish_token()) {
  150. (void)hooks::package_trust::uninstall();
  151. clear_game_targets();
  152. return false;
  153. }
  154. if (!hooks::network::install_game()) {
  155. core::log::write(core::log::Channel::client,
  156. core::log::Level::error,
  157. "ev=activate stage=game_network result=fail");
  158. if (!hooks::network::has_game_ownership()) {
  159. (void)hooks::package_trust::uninstall();
  160. clear_game_targets();
  161. }
  162. return false;
  163. }
  164. core::log::write(core::log::Channel::client,
  165. core::log::Level::info,
  166. "ev=activate stage=game_network result=ok");
  167. const bool packageKeys = targets::game::packages::is_resolved();
  168. core::log::write(core::log::Channel::client,
  169. packageKeys ? core::log::Level::info : core::log::Level::warn,
  170. packageKeys ? "ev=activate stage=package_keys result=ok"
  171. : "ev=activate stage=package_keys result=fail");
  172. // Diagnostic capture reports its own outcome and never demotes this stage.
  173. // The probe hooks only the index allocator, whose two-argument shape was read out of its own
  174. // body. The initialiser beside it is left alone: its fifth argument is passed on the stack,
  175. // and a four-argument replacement black-screened the load on 2026-08-25.
  176. (void)diagnostics::install_entity_create_probe(
  177. core::settings::get().client.stockEntityPool,
  178. core::settings::get().client.restockDrainedEntityPool);
  179. (void)hooks::retail_log::install();
  180. (void)hooks::vendor_banner::install();
  181. (void)hooks::assert_handler::install();
  182. // Read-only. At a hitch it dumps every in-flight job record from the watchdog snapshot,
  183. // which names the job and thread the in-world freeze blocks on.
  184. (void)hooks::hitch_probe::install();
  185. // Read-only. Some freezes silence the watchdog too; this watcher dumps every thread's rip
  186. // and stack from its own thread when the game stops calling the pump.
  187. (void)hooks::stall_probe::install();
  188. // A sense-record chain that stops terminating after a slice-set teardown holds the whole
  189. // frame graph. The guard logs the runaway chain and skips its walk for that tick.
  190. (void)hooks::sense_chain_guard::install();
  191. // The stock client always relays the gameplay peer channel, which cannot complete against a
  192. // loopback host. When enabled, this forces a direct connect. Off by default.
  193. (void)hooks::peer_relay::install();
  194. // The stock async-I/O wrapper reloads its singleton after pumping it and can observe the
  195. // legitimate teardown/recreate null window. This optional guard keeps the owner it pumped.
  196. (void)hooks::async_io::install();
  197. (void)hooks::config_getter::install();
  198. // Boot-step fixes scan for their own single-site targets; each reports its own outcome.
  199. (void)hooks::bootflow::install();
  200. // The teleport hooks attach whether or not the feature is on, so the interface can enable it
  201. // without a restart. Both replacements return immediately while nothing is requested.
  202. (void)hooks::teleport::install();
  203. // Noclip owns its Havok-step target, so a patch-specific miss cannot disable teleport.
  204. (void)hooks::noclip::install();
  205. // Attaches whether or not the feature is on, so the interface can enable it without a restart.
  206. (void)hooks::infinite_ammo::install();
  207. // Resolves the activity config getter here; the hold itself runs on the frame tick.
  208. (void)hooks::inactivity::install();
  209. (void)hooks::queuez::install();
  210. // The bitmap reference guard puts the none sentinel in place of a reference outside tag
  211. // space. Without it the widget's stored-reference reader faults.
  212. (void)hooks::bitmap::install();
  213. // Read-only. It reports the status word the activity msg 12 handler writes. That word is the
  214. // one thing separating "the client never saw our membership body" from "it saw it and the
  215. // world container still did not bind".
  216. (void)hooks::membership_probe::install();
  217. // Read-only. While the prologue-filler boot task runs, it logs once per second which
  218. // cinematic readiness stage is false, the thing the task's five-second timeout hides.
  219. (void)hooks::cine_probe::install();
  220. // Read-only. Logs the type-6 cinematic Auth chain: the armed gate, the body copy, each
  221. // silent start gate with the compared values, and the start outcome.
  222. (void)hooks::cine_auth_probe::install();
  223. // Retains the native handle for package placements without publishing unnamed map objects.
  224. (void)hooks::world_objects::install();
  225. content::investment::worker::activate();
  226. content::activity::scriptables::activate();
  227. return true;
  228. }
  229. } // namespace
  230. } // namespace sunrise::client::runtime
  231. namespace sunrise::client {
  232. /** Resolves main-image targets and installs required game hooks once. */
  233. bool activate_main_once() noexcept {
  234. AcquireSRWLockExclusive(&runtime::g_lock);
  235. if (runtime::g_mainStage != runtime::StageState::pending) {
  236. const bool active = runtime::g_mainStage == runtime::StageState::active;
  237. ReleaseSRWLockExclusive(&runtime::g_lock);
  238. return active;
  239. }
  240. // The image sweep dominates this call, so the pair of debug markers around it is what a
  241. // boot-time measurement reads. Both are diagnostic and stay off at the usual levels.
  242. core::log::write(
  243. core::log::Channel::client, core::log::Level::debug, "ev=activate stage=main phase=begin");
  244. // The sweep stalls whichever thread calls it, so the overlay says what is happening. It
  245. // only reaches the screen once the presentation hooks are installed.
  246. core::ui::busy::begin(core::ui::busy::Task::initialization);
  247. // Started after the overlay is up, because begin blocks for up to half a second waiting on
  248. // presents. That wait belongs to the overlay, not to the work being measured.
  249. const std::uint64_t startedTick = GetTickCount64();
  250. const bool active = runtime::activate_required_main_locked();
  251. core::log::write_elapsed(core::log::Channel::client,
  252. "ev=activate stage=main phase=complete",
  253. startedTick,
  254. active ? "ok" : "fail");
  255. core::ui::busy::end(core::ui::busy::Task::initialization);
  256. if (!active) {
  257. // A failed sweep latches too: repeating it stalls the frame loop for nothing.
  258. runtime::g_mainStage = runtime::StageState::failed;
  259. core::log::write(core::log::Channel::client,
  260. core::log::Level::error,
  261. "ev=activate stage=main result=fail");
  262. // The boot cannot reach orbit after this, so the user is told rather than left waiting.
  263. core::ui::notice::raise("Sunrise could not attach to the game. The boot will not finish.");
  264. ReleaseSRWLockExclusive(&runtime::g_lock);
  265. return false;
  266. }
  267. runtime::g_mainStage = runtime::StageState::active;
  268. core::log::write(
  269. core::log::Channel::client, core::log::Level::info, "ev=activate stage=main result=ok");
  270. ReleaseSRWLockExclusive(&runtime::g_lock);
  271. return true;
  272. }
  273. /** Installs the presentation hooks once, independently of the game image sweep. */
  274. bool activate_graphics_once() noexcept {
  275. AcquireSRWLockExclusive(&runtime::g_lock);
  276. if (runtime::g_graphicsStage != runtime::StageState::pending) {
  277. const bool active = runtime::g_graphicsStage == runtime::StageState::active;
  278. ReleaseSRWLockExclusive(&runtime::g_lock);
  279. return active;
  280. }
  281. if (!hooks::graphics::install()) {
  282. runtime::g_graphicsStage = runtime::StageState::failed;
  283. core::log::write(core::log::Channel::client,
  284. core::log::Level::error,
  285. "ev=activate stage=graphics_hooks result=fail");
  286. ReleaseSRWLockExclusive(&runtime::g_lock);
  287. return false;
  288. }
  289. runtime::g_graphicsStage = runtime::StageState::active;
  290. // The cursor guards only matter once the interface can be shown. A miss must not demote
  291. // presentation readiness, so it is logged and not propagated.
  292. (void)hooks::cursor::install();
  293. // The game reads its action keys by scanning GetKeyState every frame, which no window
  294. // procedure sees, so the polled guards carry the same terms as the cursor guards.
  295. (void)hooks::polled_input::install();
  296. core::log::write(
  297. core::log::Channel::client, core::log::Level::info, "ev=activate stage=graphics result=ok");
  298. ReleaseSRWLockExclusive(&runtime::g_lock);
  299. return true;
  300. }
  301. } // namespace sunrise::client