Просмотр исходного кода

Use native assert-handler setter

Jason Shaw 3 недель назад
Родитель
Сommit
90485cdca9

+ 15 - 15
Sunrise/src/client/hooks/assert_handler/assert_handler_lifecycle.cpp

@@ -11,21 +11,16 @@ bool g_installed{};
 
 namespace {
 
-/**
- * Exchanges the handler slot under its own page protection.
- * @param slot Resolved handler slot.
- * @param expected Value the slot must currently hold.
- * @param value Replacement pointer.
- * @return True when the slot holds the replacement after the call.
- */
-[[nodiscard]] bool exchange(std::byte** slot, const void* expected, void* value) noexcept {
-    if (slot == nullptr || *slot != expected) {
+/** Calls the native setter after the expected-value ownership check. */
+[[nodiscard]] bool set_handler(const targets::game::assert_handler::Targets& targets,
+                               const void* expected,
+                               void* value) {
+    if (targets.setter == nullptr || targets.slot == nullptr
+        || static_cast<const void*>(*targets.slot) != expected) {
         return false;
     }
-    // The slot is a writable data global, so the write needs no protection change. The game's own
-    // setter stores it with a plain move. TODO: call that setter instead of writing the slot.
-    *slot = static_cast<std::byte*>(value);
-    return true;
+    targets.setter(value);
+    return static_cast<void*>(*targets.slot) == value;
 }
 
 } // namespace
@@ -45,7 +40,7 @@ bool install() noexcept {
         return true;
     }
     const targets::game::assert_handler::Targets& resolved = targets::game::assert_handler::get();
-    const bool installed = exchange(resolved.slot, resolved.original, handler_entry_point());
+    const bool installed = set_handler(resolved, resolved.original, handler_entry_point());
     g_installed = installed;
     ReleaseSRWLockExclusive(&g_lock);
     core::log::write(core::log::Channel::client,
@@ -63,9 +58,14 @@ bool uninstall() noexcept {
         return true;
     }
     const targets::game::assert_handler::Targets& resolved = targets::game::assert_handler::get();
-    const bool restored = exchange(resolved.slot, handler_entry_point(), resolved.original);
+    const bool restored = set_handler(resolved, handler_entry_point(), resolved.original);
     g_installed = !restored;
     ReleaseSRWLockExclusive(&g_lock);
+    if (!restored) {
+        core::log::write(core::log::Channel::client,
+                         core::log::Level::warn,
+                         "ev=assert stage=uninstall result=fail reason=slot");
+    }
     return restored;
 }
 

+ 5 - 0
Sunrise/src/client/targets/game/assert_handler.h

@@ -4,12 +4,17 @@
 
 namespace sunrise::client::targets::game::assert_handler {
 
+/** Native setter that installs the process assert handler. */
+using Setter = void(__fastcall*)(void*);
+
 /** Resolved assert-reporting targets. */
 struct Targets {
     /** Slot the assert sites call through when it is non-null. */
     std::byte** slot{};
     /** Handler the game installed, retained so detach can restore it. */
     void* original{};
+    /** Setter copy that writes the selected handler slot. */
+    Setter setter{};
 };
 
 /** Clears the published assert target group. */

+ 4 - 1
Sunrise/src/client/targets/game/assert_handler/game_assert_targets.cpp

@@ -77,7 +77,7 @@ constexpr std::size_t kDisplacementSize = 4;
 
 } // namespace
 
-/** Derives the assert handler slot by voting sampled assert sites against every setter copy. */
+/** Derives the assert handler target by voting sampled assert sites against every setter copy. */
 bool derive(std::span<const patterns::ImageRange> image, Targets& output) noexcept {
     output = {};
     const patterns::Pattern setterPattern{"assert_set_handler",
@@ -93,6 +93,7 @@ bool derive(std::span<const patterns::ImageRange> image, Targets& output) noexce
     }
 
     const std::span<std::byte* const> sampled(sites.data(), siteCount);
+    Setter winnerSetter = nullptr;
     std::byte** winner = nullptr;
     std::size_t best = 0;
     std::size_t runnerUp = 0;
@@ -102,6 +103,7 @@ bool derive(std::span<const patterns::ImageRange> image, Targets& output) noexce
         if (votes > best) {
             runnerUp = best;
             best = votes;
+            winnerSetter = reinterpret_cast<Setter>(setters[index]);
             winner = candidate;
         } else if (votes > runnerUp) {
             runnerUp = votes;
@@ -114,6 +116,7 @@ bool derive(std::span<const patterns::ImageRange> image, Targets& output) noexce
     Targets resolved;
     resolved.slot = winner;
     resolved.original = *winner;
+    resolved.setter = winnerSetter;
     output = resolved;
     return true;
 }

+ 5 - 2
Sunrise/src/client/targets/game/assert_handler/game_assert_targets.h

@@ -7,12 +7,15 @@
 
 namespace sunrise::client::targets::game::assert_handler {
 
+// clang-format off
 /**
- * Derives the assert handler slot from the setter copies and the assert sites that read it.
+ * Derives the assert handler setter and slot from the setter copies and the assert sites that read
+ * it.
  * @param image Executable ranges from the main game image.
- * @param output Receives the slot and the handler currently installed in it.
+ * @param output Receives the setter, slot and handler currently installed in it.
  * @return True when one candidate wins the vote by the required margin.
  */
+// clang-format on
 [[nodiscard]] bool derive(std::span<const patterns::ImageRange> image, Targets& output) noexcept;
 
 /** @param targets Validated assert table published without failure. */