bootflow_hook_lifecycle.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. #include "bootflow_hook_lifecycle.h"
  2. #include <array>
  3. #include <atomic>
  4. #include <cstddef>
  5. #include <span>
  6. #include "internal.h"
  7. namespace sunrise::client::hooks::bootflow {
  8. namespace {
  9. std::atomic_bool g_installed{false};
  10. /** One boot-step fix that attaches a detour, in the order the group installs them. */
  11. struct Fix {
  12. StageResult (*stage)(hooking::detour::Spec&) noexcept;
  13. void (*publish)(const hooking::detour::Handle&) noexcept;
  14. };
  15. /**
  16. * Every fix that attaches a detour. `world_step` and `fade_release` are absent: they only find
  17. * addresses to call, so they open no transaction and cost the group nothing.
  18. */
  19. constexpr std::array kFixes{
  20. Fix{&stage_character_select_hold, &publish_character_select_hold},
  21. Fix{&stage_orbit_slice_set, &publish_orbit_slice_set},
  22. Fix{&stage_profile_setup_skip, &publish_profile_setup_skip},
  23. Fix{&stage_composition_check, &publish_composition_check},
  24. Fix{&stage_orbit_handoff, &publish_orbit_handoff},
  25. Fix{&stage_owner_activity_slot, &publish_owner_activity_slot},
  26. Fix{&stage_region_private, &publish_region_private},
  27. Fix{&stage_spawn_hold, &publish_spawn_hold},
  28. };
  29. /** Marks a fix that staged nothing, so no handle is ever published to it. */
  30. constexpr std::size_t kNotStaged = kFixes.size();
  31. /** One fix's place in the batch, and what it already was before staging. */
  32. struct Placement {
  33. std::size_t slot{kNotStaged};
  34. StageResult result{StageResult::unavailable};
  35. };
  36. } // namespace
  37. /**
  38. * Attaches the boot-step fixes that carry sign-in through to orbit.
  39. * Each fix stands alone at one site, so a miss on one is reported and the others still attach.
  40. *
  41. * Every resolved fix attaches in one transaction rather than one each. A transaction enlists the
  42. * threads it must suspend by walking every thread on the system, which is far more work than the
  43. * attach itself, so nine transactions cost nine of those walks and one costs one. A fix whose
  44. * target is missing simply is not in the batch, which is what keeps one miss off the others. If
  45. * the batch itself fails the fixes are retried one at a time, so a single target Detours refuses
  46. * cannot take the rest of the group down with it.
  47. * @return True when every fix attached.
  48. */
  49. bool install() noexcept {
  50. std::array<hooking::detour::Spec, kFixes.size()> specs{};
  51. std::array<hooking::detour::Handle, kFixes.size()> handles{};
  52. std::array<Placement, kFixes.size()> placement{};
  53. std::size_t staged = 0;
  54. for (std::size_t index = 0; index < kFixes.size(); ++index) {
  55. hooking::detour::Spec spec{};
  56. const StageResult result = kFixes[index].stage(spec);
  57. placement[index].result = result;
  58. if (result != StageResult::staged) {
  59. continue;
  60. }
  61. specs[staged] = spec;
  62. placement[index].slot = staged;
  63. ++staged;
  64. }
  65. if (staged != 0
  66. && !hooking::detour::install(std::span(specs).first(staged),
  67. std::span(handles).first(staged))) {
  68. // One refused target must not cost the others their fix, so the slow path stands them up
  69. // separately. It runs only when the whole batch failed, which no supported build does.
  70. for (std::size_t slot = 0; slot < staged; ++slot) {
  71. handles[slot] = {};
  72. (void)hooking::detour::install(specs[slot], handles[slot]);
  73. }
  74. }
  75. bool anyFix = false;
  76. bool everyFix = true;
  77. for (std::size_t index = 0; index < kFixes.size(); ++index) {
  78. const Placement& place = placement[index];
  79. if (place.slot == kNotStaged) {
  80. // An already-attached fix stays attached; only a missing target is a failure.
  81. anyFix = anyFix || place.result == StageResult::attached;
  82. everyFix = everyFix && place.result == StageResult::attached;
  83. continue;
  84. }
  85. const hooking::detour::Handle& handle = handles[place.slot];
  86. kFixes[index].publish(handle);
  87. anyFix = anyFix || handle.attached;
  88. everyFix = everyFix && handle.attached;
  89. }
  90. // Neither of these attaches anything, so they stay outside the transaction.
  91. const bool worldStep = install_world_step();
  92. const bool fade = install_fade_release();
  93. anyFix = anyFix || worldStep || fade;
  94. g_installed.store(anyFix, std::memory_order_release);
  95. return everyFix && worldStep && fade;
  96. }
  97. /** Detaches every boot-step fix, in the reverse order of install. */
  98. void uninstall() noexcept {
  99. uninstall_fade_release();
  100. uninstall_spawn_hold();
  101. uninstall_world_step();
  102. uninstall_region_private();
  103. uninstall_owner_activity_slot();
  104. uninstall_orbit_handoff();
  105. uninstall_composition_check();
  106. uninstall_profile_setup_skip();
  107. uninstall_orbit_slice_set();
  108. uninstall_character_select_hold();
  109. g_installed.store(false, std::memory_order_release);
  110. }
  111. /** @return True while at least one boot-step fix is attached. */
  112. bool is_installed() noexcept {
  113. return g_installed.load(std::memory_order_acquire);
  114. }
  115. } // namespace sunrise::client::hooks::bootflow