assert_handler_lifecycle.cpp 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. #include "assert_handler_lifecycle.h"
  2. #include "../../../core/logging/log.h"
  3. #include "../../targets/game/assert_handler.h"
  4. #include "assert_handler_observer.h"
  5. namespace sunrise::client::hooks::assert_handler {
  6. SRWLOCK g_lock{SRWLOCK_INIT};
  7. bool g_installed{};
  8. namespace {
  9. /**
  10. * Exchanges the handler slot under its own page protection.
  11. * @param slot Resolved handler slot.
  12. * @param expected Value the slot must currently hold.
  13. * @param value Replacement pointer.
  14. * @return True when the slot holds the replacement after the call.
  15. */
  16. [[nodiscard]] bool exchange(std::byte** slot, const void* expected, void* value) noexcept {
  17. if (slot == nullptr || *slot != expected) {
  18. return false;
  19. }
  20. // The slot is a writable data global, so the write needs no protection change. The game's own
  21. // setter stores it with a plain move. TODO: call that setter instead of writing the slot.
  22. *slot = static_cast<std::byte*>(value);
  23. return true;
  24. }
  25. } // namespace
  26. /** Replaces the game's fatal assert handler so an assert reports instead of halting. */
  27. bool install() noexcept {
  28. AcquireSRWLockExclusive(&g_lock);
  29. if (g_installed) {
  30. ReleaseSRWLockExclusive(&g_lock);
  31. return true;
  32. }
  33. if (!targets::game::assert_handler::is_resolved()) {
  34. ReleaseSRWLockExclusive(&g_lock);
  35. core::log::write(core::log::Channel::client,
  36. core::log::Level::warn,
  37. "ev=assert stage=install result=fail reason=target");
  38. return true;
  39. }
  40. const targets::game::assert_handler::Targets& resolved = targets::game::assert_handler::get();
  41. const bool installed = exchange(resolved.slot, resolved.original, handler_entry_point());
  42. g_installed = installed;
  43. ReleaseSRWLockExclusive(&g_lock);
  44. core::log::write(core::log::Channel::client,
  45. installed ? core::log::Level::info : core::log::Level::warn,
  46. installed ? "ev=assert stage=install result=ok"
  47. : "ev=assert stage=install result=fail reason=slot");
  48. return installed;
  49. }
  50. /** Restores the game's own handler so a later assert cannot call into unmapped code. */
  51. bool uninstall() noexcept {
  52. AcquireSRWLockExclusive(&g_lock);
  53. if (!g_installed) {
  54. ReleaseSRWLockExclusive(&g_lock);
  55. return true;
  56. }
  57. const targets::game::assert_handler::Targets& resolved = targets::game::assert_handler::get();
  58. const bool restored = exchange(resolved.slot, handler_entry_point(), resolved.original);
  59. g_installed = !restored;
  60. ReleaseSRWLockExclusive(&g_lock);
  61. return restored;
  62. }
  63. /** @return True while Sunrise's handler owns the slot. */
  64. bool is_installed() noexcept {
  65. AcquireSRWLockShared(&g_lock);
  66. const bool installed = g_installed;
  67. ReleaseSRWLockShared(&g_lock);
  68. return installed;
  69. }
  70. } // namespace sunrise::client::hooks::assert_handler