profile_setup_skip.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. #include <array>
  2. #include <atomic>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <cstring>
  6. #include <string_view>
  7. #include "../../../core/logging/log.h"
  8. #include "../../hooking/detour.h"
  9. #include "internal.h"
  10. namespace sunrise::client::hooks::bootflow {
  11. namespace {
  12. /**
  13. * The profile-setup step's update. Anchored on the screen-settled call, its taken branch and the
  14. * read of the step's own state field. Together they are unique to it. Every displacement is
  15. * wildcarded.
  16. */
  17. constexpr std::string_view kUpdateSignatureText =
  18. "40 53 48 83 EC ? 48 8B 05 ? ? ? ? 48 33 C4 48 89 44 24 ? 48 8B D9 E8 ? ? ? ? 84 C0 0F 84 ? "
  19. "? ? ? 8B 43 38 85 C0 75 ?";
  20. /** Compiled pattern bytes of the signature text above. */
  21. constexpr auto kUpdateSignature =
  22. signature<signature_length(kUpdateSignatureText)>(kUpdateSignatureText);
  23. /** Fields of the boot step this hook touches, as byte offsets from the step's own base. */
  24. struct StepLayout {
  25. /** The step's state, which its update switches on. */
  26. static constexpr std::size_t state = 56;
  27. };
  28. /**
  29. * States with no self-advance. The update posts a setup screen and parks in one of these until
  30. * the UI raises the investment event that acknowledges it. State 0 is the entry that posts the
  31. * first one.
  32. */
  33. constexpr std::array<std::uint32_t, 5> kWaitingStates{0, 1, 3, 5, 7};
  34. /**
  35. * State that matches no case in the update's dispatch, so it falls to the last block. That block
  36. * marks the profile record and hands off to the next step. It is the same exit the stock game
  37. * takes once every setup screen is acknowledged.
  38. */
  39. constexpr std::uint32_t kHandoffState = 9;
  40. /** The update's not-complete answer, returned when the trampoline is gone. */
  41. constexpr char kStepIncomplete = 0;
  42. using Update = char(__fastcall*)(std::byte*);
  43. hooking::detour::Handle g_handle{};
  44. std::atomic<Update> g_original{nullptr};
  45. std::atomic_bool g_reported{false};
  46. /** @return True when the step cannot advance on its own. */
  47. [[nodiscard]] bool is_waiting(std::uint32_t state) noexcept {
  48. for (const std::uint32_t waiting : kWaitingStates) {
  49. if (state == waiting) {
  50. return true;
  51. }
  52. }
  53. return false;
  54. }
  55. /**
  56. * Skips the startup setup screens. A UI event this setup never raises acknowledges them, so the
  57. * step would park forever. Moving it to the handoff state before the update runs takes the step's
  58. * own exit without posting a screen.
  59. * @param step Borrowed boot-step base pointer.
  60. * @return The update's own result.
  61. */
  62. __declspec(noinline) char __fastcall update(std::byte* step) noexcept {
  63. const Update original = g_original.load(std::memory_order_acquire);
  64. if (step != nullptr) {
  65. std::uint32_t state = 0;
  66. std::memcpy(&state, step + StepLayout::state, sizeof state);
  67. if (is_waiting(state)) {
  68. std::memcpy(step + StepLayout::state, &kHandoffState, sizeof kHandoffState);
  69. if (!g_reported.exchange(true, std::memory_order_relaxed)) {
  70. core::log::write(core::log::Channel::client,
  71. core::log::Level::info,
  72. "ev=bootflow stage=profile_setup result=skipped");
  73. }
  74. }
  75. }
  76. return original != nullptr ? original(step) : kStepIncomplete;
  77. }
  78. } // namespace
  79. /**
  80. * Stages the profile-setup skip.
  81. * @param spec Receives the target and replacement.
  82. * @return staged when the target is found, unavailable on a miss.
  83. */
  84. StageResult stage_profile_setup_skip(hooking::detour::Spec& spec) noexcept {
  85. if (g_handle.attached) {
  86. return StageResult::attached;
  87. }
  88. std::byte* const target = scan_main_image_unique(kUpdateSignature, "profile_setup_update");
  89. if (target == nullptr) {
  90. core::log::write(core::log::Channel::client,
  91. core::log::Level::warn,
  92. "ev=bootflow stage=profile_setup result=fail reason=target");
  93. return StageResult::unavailable;
  94. }
  95. spec = hooking::detour::Spec{target, reinterpret_cast<void*>(&update)};
  96. return StageResult::staged;
  97. }
  98. /** Takes the profile-setup skip's attached handle, or a detached one. */
  99. void publish_profile_setup_skip(const hooking::detour::Handle& handle) noexcept {
  100. if (!handle.attached) {
  101. core::log::write(core::log::Channel::client,
  102. core::log::Level::warn,
  103. "ev=bootflow stage=profile_setup result=fail reason=attach");
  104. return;
  105. }
  106. g_handle = handle;
  107. g_original.store(reinterpret_cast<Update>(g_handle.original), std::memory_order_release);
  108. core::log::write(core::log::Channel::client,
  109. core::log::Level::info,
  110. "ev=bootflow stage=profile_setup result=ok");
  111. }
  112. /** Detaches the profile-setup skip. */
  113. void uninstall_profile_setup_skip() noexcept {
  114. if (g_handle.attached) {
  115. (void)hooking::detour::uninstall(g_handle);
  116. }
  117. g_original.store(nullptr, std::memory_order_release);
  118. g_reported.store(false, std::memory_order_release);
  119. }
  120. } // namespace sunrise::client::hooks::bootflow