inactivity_override.cpp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. /**
  2. * Inactivity timeout override.
  3. *
  4. * The Client keeps one timeout per activity lane and ends a session whose controller has been
  5. * idle for longer. The lanes are not image data: they sit at a fixed offset inside a live object,
  6. * and the pointer to that object is stored obfuscated, so the Client reaches it through a getter
  7. * that decodes the pointer on each call. This module resolves that getter by signature and calls
  8. * it the same way, which is the same shape the camera pose block is reached by.
  9. *
  10. * No lane the Client authors is written down anywhere. A block that is not the one this module
  11. * last wrote is the Client's own, so reading before each hold both takes the value a lane is put
  12. * back to and follows an activity change, which re-authors the whole block.
  13. */
  14. #include "inactivity_override.h"
  15. #include <Windows.h>
  16. #include <algorithm>
  17. #include <array>
  18. #include <cstddef>
  19. #include <cstdint>
  20. #include <string_view>
  21. #include "../../../core/logging/log.h"
  22. #include "../../inactivity/inactivity_settings_store.h"
  23. #include "../../patterns/image_scan.h"
  24. namespace sunrise::client::hooks::inactivity {
  25. namespace {
  26. namespace settings = client::inactivity;
  27. using patterns::scan_main_image_unique;
  28. using patterns::signature;
  29. using patterns::signature_length;
  30. /**
  31. * The activity config getter. Its body is the shared shape every obfuscated pointer getter has,
  32. * so the load of its own global is what tells it apart: a RIP-relative displacement encodes a
  33. * distance rather than an address, carries no position-dependent bytes, and is the only part of
  34. * this prologue unique to this getter. Call and branch displacements stay wildcarded.
  35. */
  36. constexpr std::string_view kConfigGetterText =
  37. "40 53 48 83 EC 20 48 8B 1D 2B 10 1A 02 48 85 DB 0F 84 ? ? ? ? 48 89 5C 24 30 "
  38. "E8 ? ? ? ? 33 C3";
  39. /** Compiled pattern bytes of the config getter signature. */
  40. constexpr auto kConfigGetter = signature<signature_length(kConfigGetterText)>(kConfigGetterText);
  41. /** Where the lanes start in the object the getter returns. */
  42. constexpr std::size_t kTimeoutBlockOffset = 0xAC;
  43. /** Milliseconds between re-applications, so an activity change cannot outlast the hold. */
  44. constexpr std::uint64_t kHoldIntervalMs = 2000;
  45. /** Fourteen consecutive milliseconds, in block order. */
  46. using Lanes = std::array<std::uint32_t, settings::kActivityCount>;
  47. /** Bytes of the block. */
  48. constexpr std::size_t kBlockBytes = sizeof(Lanes);
  49. /** Returns the activity config object. The pointer in its global is obfuscated, so we call it. */
  50. using ConfigGetter = std::byte*(__fastcall*)();
  51. SRWLOCK g_lock{SRWLOCK_INIT};
  52. ConfigGetter g_getter{};
  53. /** The object the last call returned, kept only so the interface can show it. */
  54. std::uintptr_t g_object{};
  55. std::uint64_t g_nextHoldTick{};
  56. /** The block this module last wrote. Anything else in the object is the Client's own. */
  57. Lanes g_applied{};
  58. bool g_appliedValid{};
  59. /** The Client's own lanes for the activity in play. */
  60. Lanes g_captured{};
  61. bool g_capturedValid{};
  62. /** Set while a hold is in place, so releasing it writes the captured lanes exactly once. */
  63. bool g_holding{};
  64. /**
  65. * Calls the getter without faulting. The body is obfuscated game code, and it runs before the
  66. * Client has published its global on an early frame.
  67. * @return The activity config object, or null.
  68. */
  69. [[nodiscard]] std::byte* config_object() noexcept {
  70. if (g_getter == nullptr) {
  71. return nullptr;
  72. }
  73. __try {
  74. return g_getter();
  75. } __except (EXCEPTION_EXECUTE_HANDLER) {
  76. return nullptr;
  77. }
  78. }
  79. /**
  80. * Reads the block out of the object.
  81. * @param object Config object.
  82. * @param values Receives the lanes.
  83. * @return True when Windows copied all of them.
  84. */
  85. [[nodiscard]] bool read_block(const std::byte* object, Lanes& values) noexcept {
  86. SIZE_T read = 0;
  87. return ReadProcessMemory(GetCurrentProcess(),
  88. object + kTimeoutBlockOffset,
  89. values.data(),
  90. kBlockBytes,
  91. &read)
  92. != FALSE
  93. && read == kBlockBytes;
  94. }
  95. /**
  96. * Writes one run of milliseconds into the object.
  97. * @param object Config object.
  98. * @param values Lanes in block order.
  99. * @return True when Windows copied all of them.
  100. */
  101. [[nodiscard]] bool write_block(std::byte* object, const Lanes& values) noexcept {
  102. SIZE_T written = 0;
  103. return WriteProcessMemory(GetCurrentProcess(),
  104. object + kTimeoutBlockOffset,
  105. values.data(),
  106. kBlockBytes,
  107. &written)
  108. != FALSE
  109. && written == kBlockBytes;
  110. }
  111. /**
  112. * Takes the Client's own lanes, which are any lanes this module did not write.
  113. * @param current Block just read out of the object.
  114. */
  115. void capture_locked(const Lanes& current) noexcept {
  116. // A zero lane is an object the Client has published but not authored yet.
  117. const bool authored =
  118. std::none_of(current.begin(), current.end(), [](std::uint32_t value) noexcept {
  119. return value == 0;
  120. });
  121. if (!authored || (g_appliedValid && current == g_applied)) {
  122. return;
  123. }
  124. g_captured = current;
  125. g_capturedValid = true;
  126. }
  127. /**
  128. * @param configured Current configuration.
  129. * @return The lanes a hold puts in place.
  130. */
  131. [[nodiscard]] Lanes held_lanes(const settings::Settings& configured) noexcept {
  132. Lanes values = configured.custom ? configured.timeouts : settings::kDefaultTimeouts;
  133. // Orbit is held at its longest whatever the grid or the file carries, because a timeout that
  134. // fires there ends a session this Client cannot re-establish.
  135. values[settings::kOrbitLane] = settings::kMaximumTimeoutMs;
  136. return values;
  137. }
  138. /** Writes the captured lanes back and ends the hold. */
  139. void release_locked(std::byte* object) noexcept {
  140. if (!g_holding || !g_capturedValid || !write_block(object, g_captured)) {
  141. return;
  142. }
  143. g_holding = false;
  144. g_appliedValid = false;
  145. }
  146. } // namespace
  147. /** Resolves the activity config getter. */
  148. bool install() noexcept {
  149. AcquireSRWLockExclusive(&g_lock);
  150. if (g_getter != nullptr) {
  151. ReleaseSRWLockExclusive(&g_lock);
  152. return true;
  153. }
  154. std::byte* const match = scan_main_image_unique(kConfigGetter, "inactivity_config_getter");
  155. if (match == nullptr) {
  156. ReleaseSRWLockExclusive(&g_lock);
  157. core::log::write(core::log::Channel::client,
  158. core::log::Level::warn,
  159. "ev=inactivity stage=install result=fail reason=target");
  160. return false;
  161. }
  162. g_getter = reinterpret_cast<ConfigGetter>(match);
  163. ReleaseSRWLockExclusive(&g_lock);
  164. core::log::write(core::log::Channel::client,
  165. core::log::Level::info,
  166. "ev=inactivity stage=install result=ok");
  167. return true;
  168. }
  169. /** Puts the Client's own lanes back and drops the resolved getter. */
  170. void uninstall() noexcept {
  171. AcquireSRWLockExclusive(&g_lock);
  172. if (std::byte* const object = config_object(); object != nullptr) {
  173. release_locked(object);
  174. }
  175. g_getter = nullptr;
  176. g_object = 0;
  177. g_nextHoldTick = 0;
  178. g_applied = Lanes{};
  179. g_appliedValid = false;
  180. g_captured = Lanes{};
  181. g_capturedValid = false;
  182. g_holding = false;
  183. ReleaseSRWLockExclusive(&g_lock);
  184. }
  185. /** Holds the configured milliseconds, or puts back the ones the Client authored. */
  186. void poll() noexcept {
  187. const settings::Settings configured = settings::get();
  188. AcquireSRWLockExclusive(&g_lock);
  189. const std::uint64_t now = GetTickCount64();
  190. if (g_getter == nullptr || now < g_nextHoldTick) {
  191. ReleaseSRWLockExclusive(&g_lock);
  192. return;
  193. }
  194. g_nextHoldTick = now + kHoldIntervalMs;
  195. std::byte* const object = config_object();
  196. g_object = reinterpret_cast<std::uintptr_t>(object);
  197. if (object == nullptr) {
  198. ReleaseSRWLockExclusive(&g_lock);
  199. return;
  200. }
  201. if (Lanes current{}; read_block(object, current)) {
  202. capture_locked(current);
  203. }
  204. if (!configured.enabled) {
  205. release_locked(object);
  206. ReleaseSRWLockExclusive(&g_lock);
  207. return;
  208. }
  209. // Held rather than written once, because an activity change re-authors these lanes.
  210. const Lanes desired = held_lanes(configured);
  211. if (write_block(object, desired)) {
  212. g_applied = desired;
  213. g_appliedValid = true;
  214. g_holding = true;
  215. }
  216. ReleaseSRWLockExclusive(&g_lock);
  217. }
  218. /** Reports what the override reached. */
  219. Status status() noexcept {
  220. Status output{};
  221. AcquireSRWLockShared(&g_lock);
  222. output.resolved = g_getter != nullptr;
  223. output.address = g_object;
  224. output.captured = g_capturedValid;
  225. ReleaseSRWLockShared(&g_lock);
  226. return output;
  227. }
  228. } // namespace sunrise::client::hooks::inactivity