| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980 |
- #include "assert_handler_lifecycle.h"
- #include "../../../core/logging/log.h"
- #include "../../targets/game/assert_handler.h"
- #include "assert_handler_observer.h"
- namespace sunrise::client::hooks::assert_handler {
- SRWLOCK g_lock{SRWLOCK_INIT};
- 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) {
- 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;
- }
- } // namespace
- /** Replaces the game's fatal assert handler so an assert reports instead of halting. */
- bool install() noexcept {
- AcquireSRWLockExclusive(&g_lock);
- if (g_installed) {
- ReleaseSRWLockExclusive(&g_lock);
- return true;
- }
- if (!targets::game::assert_handler::is_resolved()) {
- ReleaseSRWLockExclusive(&g_lock);
- core::log::write(core::log::Channel::client,
- core::log::Level::warn,
- "ev=assert stage=install result=fail reason=target");
- 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());
- g_installed = installed;
- ReleaseSRWLockExclusive(&g_lock);
- core::log::write(core::log::Channel::client,
- installed ? core::log::Level::info : core::log::Level::warn,
- installed ? "ev=assert stage=install result=ok"
- : "ev=assert stage=install result=fail reason=slot");
- return installed;
- }
- /** Restores the game's own handler so a later assert cannot call into unmapped code. */
- bool uninstall() noexcept {
- AcquireSRWLockExclusive(&g_lock);
- if (!g_installed) {
- ReleaseSRWLockExclusive(&g_lock);
- 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);
- g_installed = !restored;
- ReleaseSRWLockExclusive(&g_lock);
- return restored;
- }
- /** @return True while Sunrise's handler owns the slot. */
- bool is_installed() noexcept {
- AcquireSRWLockShared(&g_lock);
- const bool installed = g_installed;
- ReleaseSRWLockShared(&g_lock);
- return installed;
- }
- } // namespace sunrise::client::hooks::assert_handler
|