fade_release.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. #include <array>
  2. #include <atomic>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <cstdio>
  6. #include <string_view>
  7. #include "../../../core/logging/log.h"
  8. #include "../../../core/settings/settings.h"
  9. #include "internal.h"
  10. namespace sunrise::client::hooks::bootflow {
  11. namespace {
  12. using core::log::kLineCapacity;
  13. /**
  14. * The narrow channel release: one channel, with a blend time, so the world fades in.
  15. * Anchored on the load of the channel key, then run on through the argument spills, because the
  16. * wildcarded frame size leaves the head too short to be unique.
  17. */
  18. constexpr std::string_view kReleaseSignatureText =
  19. "48 83 EC ? 8B 02 0F 57 C0 F3 0F 10 0D ? ? ? ? 48 8D 54 24 ? F3 0F 11 44 24";
  20. /** Compiled pattern bytes of the signature text above. */
  21. constexpr auto kReleaseSignature =
  22. signature<signature_length(kReleaseSignatureText)>(kReleaseSignatureText);
  23. /**
  24. * The fade manager accessor: one load-effective-address of its static object, then a return.
  25. * Every displacement is wildcarded, so the match runs past the return to stay unique. The bytes
  26. * after it are the next function's prologue and its thread-block read in this build.
  27. */
  28. constexpr std::string_view kAccessorSignatureText =
  29. "48 8D 05 ? ? ? ? C3 48 63 C0 E9 ? ? ? ? 48 83 EC ? 65 48 8B 04 25 58 00 00 00";
  30. /** Compiled pattern bytes of the signature text above. */
  31. constexpr auto kAccessorSignature =
  32. signature<signature_length(kAccessorSignatureText)>(kAccessorSignatureText);
  33. /** Byte offsets inside the accessor, used to decode the object address from its operand. */
  34. struct AccessorLayout {
  35. /** The 4-byte displacement follows the 2-byte load opcode. */
  36. static constexpr std::size_t displacement = 3;
  37. /** The instruction after the load, which the displacement is relative to. */
  38. static constexpr std::size_t nextInstruction = 7;
  39. };
  40. /** The world-transition fade channel. Stage two of the transition arms it with opaque black. */
  41. constexpr std::uint32_t kWorldTransitionChannel = 0x57572DAC;
  42. /** The channel's colour is a static initialiser, so it needs no lookup. */
  43. constexpr std::array<float, 4> kOpaqueBlack{0.0F, 0.0F, 0.0F, 1.0F};
  44. /** Blend seconds, so the world fades in rather than popping. */
  45. constexpr float kFadeInSeconds = 0.5F;
  46. using ReleaseChannel = std::int64_t(__fastcall*)(void*, std::uint32_t*, float*, float) noexcept;
  47. void* g_manager{nullptr};
  48. std::atomic<ReleaseChannel> g_release{nullptr};
  49. std::atomic_bool g_logged{false};
  50. } // namespace
  51. /** Re-arms the one line the release logs, so the next load reports its own. */
  52. void rearm_fade_release() noexcept {
  53. g_logged.store(false, std::memory_order_release);
  54. }
  55. /** Releases the world-transition fade channel. The spawn gate decides when. */
  56. void release_world_fade() noexcept {
  57. const ReleaseChannel release = g_release.load(std::memory_order_acquire);
  58. if (release == nullptr || g_manager == nullptr || !core::settings::get().client.fadeRelease) {
  59. return;
  60. }
  61. std::uint32_t channel = kWorldTransitionChannel;
  62. std::array<float, 4> colour = kOpaqueBlack;
  63. (void)release(g_manager, &channel, colour.data(), kFadeInSeconds);
  64. if (g_logged.exchange(true, std::memory_order_relaxed)) {
  65. return;
  66. }
  67. std::array<char, kLineCapacity> line{};
  68. const int written = std::snprintf(line.data(),
  69. line.size(),
  70. "ev=bootflow stage=fade_release result=issued channel=0x%X",
  71. kWorldTransitionChannel);
  72. if (written > 0) {
  73. core::log::write(core::log::Channel::client,
  74. core::log::Level::info,
  75. {line.data(), static_cast<std::size_t>(written)});
  76. }
  77. }
  78. /** Finds the fade release and its manager object. */
  79. bool install_fade_release() noexcept {
  80. std::byte* const release = scan_main_image_unique(kReleaseSignature, "fade_release_channel");
  81. std::byte* const accessor = scan_main_image_unique(kAccessorSignature, "fade_manager_accessor");
  82. if (release == nullptr || accessor == nullptr) {
  83. core::log::write(core::log::Channel::client,
  84. core::log::Level::warn,
  85. "ev=bootflow stage=fade_release result=fail reason=target");
  86. return false;
  87. }
  88. // The manager is the static object the accessor returns. The address comes from that
  89. // instruction's own operand, not from a stored offset.
  90. g_manager = resolve_relative(accessor + AccessorLayout::displacement,
  91. accessor + AccessorLayout::nextInstruction);
  92. g_release.store(reinterpret_cast<ReleaseChannel>(release), std::memory_order_release);
  93. core::log::write(core::log::Channel::client,
  94. core::log::Level::info,
  95. "ev=bootflow stage=fade_release result=ok");
  96. return true;
  97. }
  98. /** Clears the fade release it found. */
  99. void uninstall_fade_release() noexcept {
  100. g_release.store(nullptr, std::memory_order_release);
  101. g_manager = nullptr;
  102. g_logged.store(false, std::memory_order_release);
  103. }
  104. } // namespace sunrise::client::hooks::bootflow