src/internal/xinput_raw_scope.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "internal/xinput_raw_scope.hpp" | ||
| 2 | |||
| 3 | #include <windows.h> | ||
| 4 | |||
| 5 | #include <array> | ||
| 6 | #include <atomic> | ||
| 7 | #include <cstddef> | ||
| 8 | #include <cstdint> | ||
| 9 | #include <cwchar> | ||
| 10 | #include <limits> | ||
| 11 | #include <new> | ||
| 12 | |||
| 13 | namespace DetourModKit | ||
| 14 | { | ||
| 15 | namespace detail | ||
| 16 | { | ||
| 17 | namespace | ||
| 18 | { | ||
| 19 | constexpr std::uint64_t DESCRIPTOR_MAGIC = 0x444D4B5849524157ULL; | ||
| 20 | constexpr std::uint32_t DESCRIPTOR_VERSION = 3; | ||
| 21 | constexpr DWORD DESCRIPTOR_WAIT_MS = 2000; | ||
| 22 | constexpr std::uint64_t ADMISSION_CLOSED = std::uint64_t{1} << 63; | ||
| 23 | constexpr std::uint64_t SCOPE_COUNT_MASK = ADMISSION_CLOSED - 1; | ||
| 24 | constexpr std::uintptr_t CONSUME_BUTTONS_MASK = 0xFFFF; | ||
| 25 | constexpr unsigned CONSUME_EXPECTED_SHIFT = 16; | ||
| 26 | constexpr std::uintptr_t CONSUME_EXPECTED_MASK = CONSUME_BUTTONS_MASK << CONSUME_EXPECTED_SHIFT; | ||
| 27 | constexpr std::uintptr_t CONSUME_VALID = std::uintptr_t{1} << 32; | ||
| 28 | constexpr std::uintptr_t CONSUME_DEPTH_MASK = (std::uintptr_t{1} << 29) - 1; | ||
| 29 | constexpr unsigned CONSUME_DEPTH_SHIFT = 33; | ||
| 30 | constexpr unsigned CONSUME_USER_SHIFT = 62; | ||
| 31 | constexpr DWORD CONSUME_USER_MAX = 3; | ||
| 32 | static_assert(sizeof(std::uintptr_t) == 8); | ||
| 33 | |||
| 34 | struct RawDescriptor | ||
| 35 | { | ||
| 36 | std::uint64_t magic; | ||
| 37 | std::uint32_t version; | ||
| 38 | std::uint32_t bytes; | ||
| 39 | std::uint64_t canonical_address; | ||
| 40 | std::uint64_t mutex_value; | ||
| 41 | std::uint64_t mapping_value; | ||
| 42 | std::uint64_t alias_probe; | ||
| 43 | std::uint32_t tls_index; | ||
| 44 | std::uint32_t owners; | ||
| 45 | std::uint32_t poisoned; | ||
| 46 | std::uint32_t reserved; | ||
| 47 | std::uint32_t consume_identity_index; | ||
| 48 | std::uint32_t consume_metadata_index; | ||
| 49 | volatile LONG consume_poison; | ||
| 50 | std::uint32_t consume_reserved; | ||
| 51 | }; | ||
| 52 | static_assert(sizeof(RawDescriptor) == 80); | ||
| 53 | static_assert(offsetof(RawDescriptor, tls_index) == 48); | ||
| 54 | |||
| 55 | SRWLOCK s_owner_lock = SRWLOCK_INIT; | ||
| 56 | RawDescriptor *s_descriptor{nullptr}; | ||
| 57 | std::uint32_t s_local_owners{0}; | ||
| 58 | bool s_retained_registration{false}; | ||
| 59 | std::atomic<DWORD> s_cached_index{TLS_OUT_OF_INDEXES}; | ||
| 60 | std::atomic<DWORD> s_cached_identity_index{TLS_OUT_OF_INDEXES}; | ||
| 61 | std::atomic<DWORD> s_cached_metadata_index{TLS_OUT_OF_INDEXES}; | ||
| 62 | std::atomic<std::uint64_t> s_scope_state{ADMISSION_CLOSED}; | ||
| 63 | |||
| 64 | class LastErrorGuard | ||
| 65 | { | ||
| 66 | public: | ||
| 67 | 39630 | LastErrorGuard() noexcept : m_error(::GetLastError()) {} | |
| 68 | 39622 | ~LastErrorGuard() noexcept { ::SetLastError(m_error); } | |
| 69 | LastErrorGuard(const LastErrorGuard &) = delete; | ||
| 70 | LastErrorGuard &operator=(const LastErrorGuard &) = delete; | ||
| 71 | LastErrorGuard(LastErrorGuard &&) = delete; | ||
| 72 | LastErrorGuard &operator=(LastErrorGuard &&) = delete; | ||
| 73 | |||
| 74 | private: | ||
| 75 | DWORD m_error; | ||
| 76 | }; | ||
| 77 | |||
| 78 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 79 | constexpr std::size_t FAILURE_THREADS = 8; | ||
| 80 | std::array<std::atomic<std::uint64_t>, FAILURE_THREADS> s_failures{}; | ||
| 81 | |||
| 82 | 46688 | [[nodiscard]] bool failure_for_thread(XInputRawScopeFailure failure) noexcept | |
| 83 | { | ||
| 84 | const std::uint64_t expected = | ||
| 85 | 46688 | (static_cast<std::uint64_t>(::GetCurrentThreadId()) << 8) | static_cast<std::uint8_t>(failure); | |
| 86 | 420000 | for (const auto &slot : s_failures) | |
| 87 | { | ||
| 88 | 746675 | if (slot.load(std::memory_order_acquire) == expected) | |
| 89 | { | ||
| 90 | 22 | return true; | |
| 91 | } | ||
| 92 | } | ||
| 93 | 46660 | return false; | |
| 94 | } | ||
| 95 | #endif | ||
| 96 | |||
| 97 | 2668 | [[nodiscard]] HANDLE descriptor_mutex(const RawDescriptor &descriptor) noexcept | |
| 98 | { | ||
| 99 | 2668 | return reinterpret_cast<HANDLE>(static_cast<std::uintptr_t>(descriptor.mutex_value)); | |
| 100 | } | ||
| 101 | |||
| 102 | 1920 | [[nodiscard]] bool descriptor_header_valid(const RawDescriptor &descriptor) noexcept | |
| 103 | { | ||
| 104 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 105 | 1920 | const std::uint32_t expected_version = failure_for_thread(XInputRawScopeFailure::DescriptorVersion) | |
| 106 | 1920 | ? DESCRIPTOR_VERSION + 1 | |
| 107 | 1920 | : DESCRIPTOR_VERSION; | |
| 108 | #else | ||
| 109 | constexpr std::uint32_t expected_version = DESCRIPTOR_VERSION; | ||
| 110 | #endif | ||
| 111 | 1919 | return descriptor.magic == DESCRIPTOR_MAGIC && descriptor.version == expected_version && | |
| 112 | 5755 | descriptor.bytes == sizeof(RawDescriptor) && descriptor.reserved == 0 && | |
| 113 | 3836 | descriptor.consume_reserved == 0; | |
| 114 | } | ||
| 115 | |||
| 116 | 293 | [[nodiscard]] bool canonical_alias(RawDescriptor *view, RawDescriptor *canonical) noexcept | |
| 117 | { | ||
| 118 | 293 | MEMORY_BASIC_INFORMATION region{}; | |
| 119 | 292 | if (canonical == nullptr || ::VirtualQuery(canonical, ®ion, sizeof(region)) != sizeof(region) || | |
| 120 | 292 | region.BaseAddress != canonical || region.State != MEM_COMMIT || region.Type != MEM_MAPPED || | |
| 121 | 585 | region.Protect != PAGE_READWRITE || region.RegionSize < sizeof(RawDescriptor)) | |
| 122 | { | ||
| 123 | 1 | return false; | |
| 124 | } | ||
| 125 | 292 | volatile std::uint64_t &written = view->alias_probe; | |
| 126 | 292 | const volatile std::uint64_t &observed = canonical->alias_probe; | |
| 127 | 292 | const std::uint64_t original = written; | |
| 128 | 292 | const std::uint64_t probe = original ^ 1; | |
| 129 | 292 | written = probe; | |
| 130 | 292 | const bool same = observed == probe; | |
| 131 | 292 | written = original; | |
| 132 | 292 | return same && observed == original; | |
| 133 | } | ||
| 134 | |||
| 135 | 568 | [[nodiscard]] RawDescriptor *connect_descriptor(DWORD &error) noexcept | |
| 136 | { | ||
| 137 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 138 | 568 | if (failure_for_thread(XInputRawScopeFailure::DescriptorInit)) | |
| 139 | { | ||
| 140 | ✗ | error = ERROR_NOT_ENOUGH_MEMORY; | |
| 141 | ✗ | return nullptr; | |
| 142 | } | ||
| 143 | #endif | ||
| 144 | 568 | FILETIME created{}, exited{}, kernel{}, user{}; | |
| 145 | 568 | if (!::GetProcessTimes(::GetCurrentProcess(), &created, &exited, &kernel, &user)) | |
| 146 | { | ||
| 147 | ✗ | error = ::GetLastError(); | |
| 148 | ✗ | return nullptr; | |
| 149 | } | ||
| 150 | 568 | wchar_t mutex_name[128]{}; | |
| 151 | 568 | wchar_t mapping_name[128]{}; | |
| 152 | 1136 | const int mutex_length = std::swprintf( | |
| 153 | mutex_name, | ||
| 154 | std::size(mutex_name), | ||
| 155 | L"Local\\DMK.XInputRaw.%lu.%08lx%08lx.Lock", | ||
| 156 | ::GetCurrentProcessId(), | ||
| 157 | created.dwHighDateTime, | ||
| 158 | created.dwLowDateTime | ||
| 159 | ); | ||
| 160 | 1136 | const int mapping_length = std::swprintf( | |
| 161 | mapping_name, | ||
| 162 | std::size(mapping_name), | ||
| 163 | L"Local\\DMK.XInputRaw.%lu.%08lx%08lx.Data", | ||
| 164 | ::GetCurrentProcessId(), | ||
| 165 | created.dwHighDateTime, | ||
| 166 | created.dwLowDateTime | ||
| 167 | ); | ||
| 168 | 568 | if (mutex_length < 0 || mapping_length < 0) | |
| 169 | { | ||
| 170 | ✗ | error = ERROR_INSUFFICIENT_BUFFER; | |
| 171 | ✗ | return nullptr; | |
| 172 | } | ||
| 173 | 568 | const HANDLE mutex = ::CreateMutexW(nullptr, FALSE, mutex_name); | |
| 174 | 568 | if (mutex == nullptr) | |
| 175 | { | ||
| 176 | ✗ | error = ::GetLastError(); | |
| 177 | ✗ | return nullptr; | |
| 178 | } | ||
| 179 | 568 | const DWORD acquired = ::WaitForSingleObject(mutex, DESCRIPTOR_WAIT_MS); | |
| 180 | 568 | if (acquired != WAIT_OBJECT_0 && acquired != WAIT_ABANDONED) | |
| 181 | { | ||
| 182 | ✗ | error = acquired == WAIT_FAILED ? ::GetLastError() : ERROR_TIMEOUT; | |
| 183 | ✗ | ::CloseHandle(mutex); | |
| 184 | ✗ | return nullptr; | |
| 185 | } | ||
| 186 | 568 | const HANDLE mapping = ::CreateFileMappingW( | |
| 187 | INVALID_HANDLE_VALUE, | ||
| 188 | nullptr, | ||
| 189 | PAGE_READWRITE, | ||
| 190 | 0, | ||
| 191 | static_cast<DWORD>(sizeof(RawDescriptor)), | ||
| 192 | mapping_name | ||
| 193 | ); | ||
| 194 | 568 | const DWORD mapping_error = ::GetLastError(); | |
| 195 | 568 | const bool existing = mapping != nullptr && mapping_error == ERROR_ALREADY_EXISTS; | |
| 196 | auto *const view = | ||
| 197 | mapping == nullptr | ||
| 198 | 568 | ? nullptr | |
| 199 | : static_cast<RawDescriptor *>( | ||
| 200 | 568 | ::MapViewOfFile(mapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, sizeof(RawDescriptor)) | |
| 201 | ✗ | ); | |
| 202 | 568 | error = mapping == nullptr ? mapping_error : view == nullptr ? ::GetLastError() : ERROR_INVALID_DATA; | |
| 203 | 568 | RawDescriptor *result = nullptr; | |
| 204 | 568 | bool process_owner = false; | |
| 205 | 568 | if (view != nullptr) | |
| 206 | { | ||
| 207 | 568 | if (!existing && acquired == WAIT_OBJECT_0) | |
| 208 | { | ||
| 209 | 544 | ::new (static_cast<void *>(view)) RawDescriptor{ | |
| 210 | .magic = 0, | ||
| 211 | .version = DESCRIPTOR_VERSION, | ||
| 212 | .bytes = sizeof(RawDescriptor), | ||
| 213 | .canonical_address = reinterpret_cast<std::uintptr_t>(view), | ||
| 214 | .mutex_value = reinterpret_cast<std::uintptr_t>(mutex), | ||
| 215 | .mapping_value = reinterpret_cast<std::uintptr_t>(mapping), | ||
| 216 | .alias_probe = 0, | ||
| 217 | .tls_index = TLS_OUT_OF_INDEXES, | ||
| 218 | .owners = 0, | ||
| 219 | .poisoned = 0, | ||
| 220 | .reserved = 0, | ||
| 221 | .consume_identity_index = TLS_OUT_OF_INDEXES, | ||
| 222 | .consume_metadata_index = TLS_OUT_OF_INDEXES, | ||
| 223 | .consume_poison = 0, | ||
| 224 | .consume_reserved = 0, | ||
| 225 | 272 | }; | |
| 226 | 272 | view->magic = DESCRIPTOR_MAGIC; | |
| 227 | 272 | result = view; | |
| 228 | 272 | process_owner = true; | |
| 229 | } | ||
| 230 | 296 | else if (descriptor_header_valid(*view)) | |
| 231 | { | ||
| 232 | 293 | auto *const canonical = | |
| 233 | 293 | reinterpret_cast<RawDescriptor *>(static_cast<std::uintptr_t>(view->canonical_address)); | |
| 234 | 293 | if (canonical_alias(view, canonical) && descriptor_header_valid(*canonical)) | |
| 235 | { | ||
| 236 | 290 | result = canonical; | |
| 237 | 290 | if (acquired == WAIT_ABANDONED) | |
| 238 | { | ||
| 239 | ✗ | result->poisoned = 1; | |
| 240 | } | ||
| 241 | } | ||
| 242 | } | ||
| 243 | 568 | if (!process_owner) | |
| 244 | { | ||
| 245 | 296 | ::UnmapViewOfFile(view); | |
| 246 | } | ||
| 247 | } | ||
| 248 | 568 | if (mapping != nullptr && !process_owner) | |
| 249 | { | ||
| 250 | 296 | ::CloseHandle(mapping); | |
| 251 | } | ||
| 252 | 568 | ::ReleaseMutex(mutex); | |
| 253 | 568 | if (!process_owner) | |
| 254 | { | ||
| 255 | 296 | ::CloseHandle(mutex); | |
| 256 | } | ||
| 257 | 568 | return result; | |
| 258 | } | ||
| 259 | |||
| 260 | 1334 | [[nodiscard]] bool lock_descriptor(RawDescriptor &descriptor, DWORD &error) noexcept | |
| 261 | { | ||
| 262 | 1334 | const DWORD acquired = ::WaitForSingleObject(descriptor_mutex(descriptor), DESCRIPTOR_WAIT_MS); | |
| 263 | 1334 | if (acquired == WAIT_ABANDONED) | |
| 264 | { | ||
| 265 | ✗ | descriptor.poisoned = 1; | |
| 266 | ✗ | ::ReleaseMutex(descriptor_mutex(descriptor)); | |
| 267 | ✗ | error = ERROR_INVALID_DATA; | |
| 268 | ✗ | return false; | |
| 269 | } | ||
| 270 | 1334 | if (acquired != WAIT_OBJECT_0) | |
| 271 | { | ||
| 272 | ✗ | error = acquired == WAIT_FAILED ? ::GetLastError() : ERROR_TIMEOUT; | |
| 273 | ✗ | return false; | |
| 274 | } | ||
| 275 | 3327 | const bool indices_empty = descriptor.tls_index == TLS_OUT_OF_INDEXES && | |
| 276 | 1993 | descriptor.consume_identity_index == TLS_OUT_OF_INDEXES && | |
| 277 | 659 | descriptor.consume_metadata_index == TLS_OUT_OF_INDEXES; | |
| 278 | 3343 | const bool indices_live = descriptor.tls_index != TLS_OUT_OF_INDEXES && | |
| 279 | 675 | descriptor.consume_identity_index != TLS_OUT_OF_INDEXES && | |
| 280 | 675 | descriptor.consume_metadata_index != TLS_OUT_OF_INDEXES && | |
| 281 | 675 | descriptor.tls_index != descriptor.consume_identity_index && | |
| 282 | 2684 | descriptor.tls_index != descriptor.consume_metadata_index && | |
| 283 | 675 | descriptor.consume_identity_index != descriptor.consume_metadata_index; | |
| 284 | 2667 | if (!descriptor_header_valid(descriptor) || descriptor.poisoned != 0 || | |
| 285 | 1333 | (descriptor.owners == 0 ? !indices_empty : !indices_live)) | |
| 286 | { | ||
| 287 | 1 | error = ERROR_INVALID_DATA; | |
| 288 | 1 | ::ReleaseMutex(descriptor_mutex(descriptor)); | |
| 289 | 1 | return false; | |
| 290 | } | ||
| 291 | 1333 | return true; | |
| 292 | } | ||
| 293 | |||
| 294 | 5867 | [[nodiscard]] bool store_depth(DWORD index, std::uintptr_t depth, [[maybe_unused]] bool entry) noexcept | |
| 295 | { | ||
| 296 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 297 | 5867 | if (failure_for_thread(entry ? XInputRawScopeFailure::EntryStore : XInputRawScopeFailure::Restore)) | |
| 298 | { | ||
| 299 | 5 | return false; | |
| 300 | } | ||
| 301 | #endif | ||
| 302 | 5862 | return ::TlsSetValue(index, reinterpret_cast<void *>(depth)) != FALSE; | |
| 303 | } | ||
| 304 | |||
| 305 | enum class ConsumeStore : std::uint8_t | ||
| 306 | { | ||
| 307 | EntryIdentity, | ||
| 308 | EntryMetadata, | ||
| 309 | Rollback, | ||
| 310 | Original, | ||
| 311 | RestoreIdentity, | ||
| 312 | RestoreMetadata, | ||
| 313 | }; | ||
| 314 | |||
| 315 | [[nodiscard]] bool | ||
| 316 | 28519 | store_consume(DWORD index, std::uintptr_t value, [[maybe_unused]] ConsumeStore phase) noexcept | |
| 317 | { | ||
| 318 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 319 | 85555 | const XInputRawScopeFailure fault = [&]() noexcept -> XInputRawScopeFailure | |
| 320 | { | ||
| 321 | 28518 | switch (phase) | |
| 322 | { | ||
| 323 | 6936 | case ConsumeStore::EntryIdentity: | |
| 324 | 6936 | return XInputRawScopeFailure::ConsumeEntryIdentity; | |
| 325 | 7175 | case ConsumeStore::EntryMetadata: | |
| 326 | 7175 | return XInputRawScopeFailure::ConsumeEntryMetadata; | |
| 327 | 6 | case ConsumeStore::Rollback: | |
| 328 | 6 | return XInputRawScopeFailure::ConsumeEntryRollback; | |
| 329 | 294 | case ConsumeStore::Original: | |
| 330 | 294 | return XInputRawScopeFailure::ConsumeOriginalStore; | |
| 331 | 6934 | case ConsumeStore::RestoreIdentity: | |
| 332 | 6934 | return XInputRawScopeFailure::ConsumeRestoreIdentity; | |
| 333 | 7173 | case ConsumeStore::RestoreMetadata: | |
| 334 | 7173 | return XInputRawScopeFailure::ConsumeRestoreMetadata; | |
| 335 | } | ||
| 336 | ✗ | return XInputRawScopeFailure::None; | |
| 337 | 28519 | }(); | |
| 338 | 35692 | if (failure_for_thread(fault) || (phase == ConsumeStore::EntryMetadata && | |
| 339 | 7174 | failure_for_thread(XInputRawScopeFailure::ConsumeEntryRollback))) | |
| 340 | { | ||
| 341 | 12 | return false; | |
| 342 | } | ||
| 343 | #endif | ||
| 344 | 28507 | return ::TlsSetValue(index, reinterpret_cast<void *>(value)) != FALSE; | |
| 345 | } | ||
| 346 | |||
| 347 | 12 | void poison_consume_context() noexcept | |
| 348 | { | ||
| 349 | 12 | ::InterlockedExchange(&s_descriptor->consume_poison, 1); | |
| 350 | 12 | } | |
| 351 | |||
| 352 | 7949 | [[nodiscard]] std::uint32_t consume_depth(std::uintptr_t metadata) noexcept | |
| 353 | { | ||
| 354 | 7949 | return static_cast<std::uint32_t>((metadata >> CONSUME_DEPTH_SHIFT) & CONSUME_DEPTH_MASK); | |
| 355 | } | ||
| 356 | |||
| 357 | 1015 | [[nodiscard]] DWORD consume_user(std::uintptr_t metadata) noexcept | |
| 358 | { | ||
| 359 | 1015 | return static_cast<DWORD>(metadata >> CONSUME_USER_SHIFT); | |
| 360 | } | ||
| 361 | |||
| 362 | 15897 | [[nodiscard]] bool read_consume(DWORD index, std::uintptr_t &value) noexcept | |
| 363 | { | ||
| 364 | 15897 | value = reinterpret_cast<std::uintptr_t>(::TlsGetValue(index)); | |
| 365 | 15898 | return ::GetLastError() == ERROR_SUCCESS; | |
| 366 | } | ||
| 367 | |||
| 368 | 654 | [[nodiscard]] bool reserve_scope_indices(RawDescriptor &descriptor, DWORD &error) noexcept | |
| 369 | { | ||
| 370 | 654 | std::array<DWORD, 3> indices{TLS_OUT_OF_INDEXES, TLS_OUT_OF_INDEXES, TLS_OUT_OF_INDEXES}; | |
| 371 | 5220 | for (std::size_t i = 0; i < indices.size(); ++i) | |
| 372 | { | ||
| 373 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 374 | 3264 | const XInputRawScopeFailure fault = i == 0 ? XInputRawScopeFailure::Reservation | |
| 375 | 1305 | : i == 1 ? XInputRawScopeFailure::IdentityReservation | |
| 376 | : XInputRawScopeFailure::MetadataReservation; | ||
| 377 | 1959 | if (failure_for_thread(fault)) | |
| 378 | { | ||
| 379 | 3 | error = ERROR_NOT_ENOUGH_MEMORY; | |
| 380 | } | ||
| 381 | else | ||
| 382 | #endif | ||
| 383 | { | ||
| 384 | 1956 | indices[i] = ::TlsAlloc(); | |
| 385 | 1956 | if (indices[i] == TLS_OUT_OF_INDEXES) | |
| 386 | { | ||
| 387 | ✗ | error = ::GetLastError(); | |
| 388 | } | ||
| 389 | } | ||
| 390 | 1959 | if (indices[i] == TLS_OUT_OF_INDEXES) | |
| 391 | { | ||
| 392 | 12 | for (const DWORD allocated : indices) | |
| 393 | { | ||
| 394 | 9 | if (allocated != TLS_OUT_OF_INDEXES && !::TlsFree(allocated)) | |
| 395 | { | ||
| 396 | ✗ | descriptor.poisoned = 1; | |
| 397 | } | ||
| 398 | } | ||
| 399 | 3 | return false; | |
| 400 | } | ||
| 401 | } | ||
| 402 | 651 | descriptor.tls_index = indices[0]; | |
| 403 | 651 | descriptor.consume_identity_index = indices[1]; | |
| 404 | 651 | descriptor.consume_metadata_index = indices[2]; | |
| 405 | 651 | ::InterlockedExchange(&descriptor.consume_poison, 0); | |
| 406 | 651 | return true; | |
| 407 | } | ||
| 408 | |||
| 409 | 10125 | [[nodiscard]] bool admit_scope() noexcept | |
| 410 | { | ||
| 411 | 10125 | std::uint64_t state = s_scope_state.load(std::memory_order_seq_cst); | |
| 412 | 10125 | while ((state & ADMISSION_CLOSED) == 0 && (state & SCOPE_COUNT_MASK) != SCOPE_COUNT_MASK) | |
| 413 | { | ||
| 414 | 20244 | if (s_scope_state.compare_exchange_weak( | |
| 415 | state, | ||
| 416 | state + 1, | ||
| 417 | std::memory_order_seq_cst, | ||
| 418 | std::memory_order_seq_cst | ||
| 419 | )) | ||
| 420 | { | ||
| 421 | 10122 | return true; | |
| 422 | } | ||
| 423 | } | ||
| 424 | 3 | return false; | |
| 425 | } | ||
| 426 | } // namespace | ||
| 427 | |||
| 428 | 771 | bool acquire_xinput_raw_scope_owner(DWORD *error) noexcept | |
| 429 | { | ||
| 430 | 771 | const LastErrorGuard preserve_error; | |
| 431 | 771 | DWORD failure = ERROR_SUCCESS; | |
| 432 | 771 | ::AcquireSRWLockExclusive(&s_owner_lock); | |
| 433 | 771 | if (s_local_owners != 0 || s_retained_registration) | |
| 434 | { | ||
| 435 | 88 | if (s_local_owners == std::numeric_limits<std::uint32_t>::max()) | |
| 436 | { | ||
| 437 | ✗ | failure = ERROR_TOO_MANY_OPEN_FILES; | |
| 438 | } | ||
| 439 | else | ||
| 440 | { | ||
| 441 | 88 | ++s_local_owners; | |
| 442 | 88 | s_retained_registration = false; | |
| 443 | s_scope_state.fetch_and(SCOPE_COUNT_MASK, std::memory_order_seq_cst); | ||
| 444 | 88 | ::ReleaseSRWLockExclusive(&s_owner_lock); | |
| 445 | 88 | if (error != nullptr) | |
| 446 | { | ||
| 447 | ✗ | *error = ERROR_SUCCESS; | |
| 448 | } | ||
| 449 | 88 | return true; | |
| 450 | } | ||
| 451 | } | ||
| 452 | else | ||
| 453 | { | ||
| 454 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 455 | 683 | if (failure_for_thread(XInputRawScopeFailure::DescriptorInit)) | |
| 456 | { | ||
| 457 | 1 | failure = ERROR_NOT_ENOUGH_MEMORY; | |
| 458 | } | ||
| 459 | else | ||
| 460 | #endif | ||
| 461 | { | ||
| 462 | 682 | if (s_descriptor == nullptr) | |
| 463 | { | ||
| 464 | 568 | s_descriptor = connect_descriptor(failure); | |
| 465 | } | ||
| 466 | 682 | if (s_descriptor != nullptr && lock_descriptor(*s_descriptor, failure)) | |
| 467 | { | ||
| 468 | 675 | if (s_descriptor->owners == std::numeric_limits<std::uint32_t>::max()) | |
| 469 | { | ||
| 470 | ✗ | failure = ERROR_TOO_MANY_OPEN_FILES; | |
| 471 | } | ||
| 472 | else | ||
| 473 | { | ||
| 474 | 675 | if (s_descriptor->tls_index == TLS_OUT_OF_INDEXES) | |
| 475 | { | ||
| 476 | 654 | (void)reserve_scope_indices(*s_descriptor, failure); | |
| 477 | } | ||
| 478 | 675 | if (s_descriptor->tls_index != TLS_OUT_OF_INDEXES) | |
| 479 | { | ||
| 480 | 672 | ++s_descriptor->owners; | |
| 481 | 672 | s_local_owners = 1; | |
| 482 | 672 | s_cached_identity_index.store( | |
| 483 | 672 | s_descriptor->consume_identity_index, | |
| 484 | std::memory_order_relaxed | ||
| 485 | ); | ||
| 486 | 672 | s_cached_metadata_index.store( | |
| 487 | 672 | s_descriptor->consume_metadata_index, | |
| 488 | std::memory_order_relaxed | ||
| 489 | ); | ||
| 490 | 672 | s_cached_index.store(s_descriptor->tls_index, std::memory_order_release); | |
| 491 | s_scope_state.store(0, std::memory_order_seq_cst); | ||
| 492 | 672 | ::ReleaseMutex(descriptor_mutex(*s_descriptor)); | |
| 493 | 672 | ::ReleaseSRWLockExclusive(&s_owner_lock); | |
| 494 | 672 | if (error != nullptr) | |
| 495 | { | ||
| 496 | ✗ | *error = ERROR_SUCCESS; | |
| 497 | } | ||
| 498 | 672 | return true; | |
| 499 | } | ||
| 500 | } | ||
| 501 | 3 | ::ReleaseMutex(descriptor_mutex(*s_descriptor)); | |
| 502 | } | ||
| 503 | } | ||
| 504 | } | ||
| 505 | 11 | ::ReleaseSRWLockExclusive(&s_owner_lock); | |
| 506 | 11 | if (error != nullptr) | |
| 507 | { | ||
| 508 | 11 | *error = failure; | |
| 509 | } | ||
| 510 | 11 | return false; | |
| 511 | 771 | } | |
| 512 | |||
| 513 | 738 | void release_xinput_raw_scope_owner() noexcept | |
| 514 | { | ||
| 515 | 738 | const LastErrorGuard preserve_error; | |
| 516 | 738 | ::AcquireSRWLockExclusive(&s_owner_lock); | |
| 517 | 738 | if (s_local_owners == 0) | |
| 518 | { | ||
| 519 | ✗ | ::ReleaseSRWLockExclusive(&s_owner_lock); | |
| 520 | ✗ | return; | |
| 521 | } | ||
| 522 | 738 | if (--s_local_owners != 0) | |
| 523 | { | ||
| 524 | 86 | ::ReleaseSRWLockExclusive(&s_owner_lock); | |
| 525 | 86 | return; | |
| 526 | } | ||
| 527 | 652 | const std::uint64_t scopes = s_scope_state.fetch_or(ADMISSION_CLOSED, std::memory_order_seq_cst); | |
| 528 | 652 | DWORD error = ERROR_SUCCESS; | |
| 529 | 652 | if ((scopes & SCOPE_COUNT_MASK) != 0 || s_descriptor == nullptr || !lock_descriptor(*s_descriptor, error)) | |
| 530 | { | ||
| 531 | 2 | s_retained_registration = true; | |
| 532 | 2 | ::ReleaseSRWLockExclusive(&s_owner_lock); | |
| 533 | 2 | return; | |
| 534 | } | ||
| 535 | s_cached_index.store(TLS_OUT_OF_INDEXES, std::memory_order_release); | ||
| 536 | s_cached_identity_index.store(TLS_OUT_OF_INDEXES, std::memory_order_relaxed); | ||
| 537 | s_cached_metadata_index.store(TLS_OUT_OF_INDEXES, std::memory_order_relaxed); | ||
| 538 | 650 | if (--s_descriptor->owners == 0) | |
| 539 | { | ||
| 540 | const std::array<DWORD, 3> indices{ | ||
| 541 | 629 | s_descriptor->tls_index, | |
| 542 | 629 | s_descriptor->consume_identity_index, | |
| 543 | 629 | s_descriptor->consume_metadata_index, | |
| 544 | 629 | }; | |
| 545 | 629 | s_descriptor->tls_index = TLS_OUT_OF_INDEXES; | |
| 546 | 629 | s_descriptor->consume_identity_index = TLS_OUT_OF_INDEXES; | |
| 547 | 629 | s_descriptor->consume_metadata_index = TLS_OUT_OF_INDEXES; | |
| 548 | 2516 | for (const DWORD index : indices) | |
| 549 | { | ||
| 550 | 1887 | if (!::TlsFree(index)) | |
| 551 | { | ||
| 552 | ✗ | s_descriptor->poisoned = 1; | |
| 553 | } | ||
| 554 | } | ||
| 555 | } | ||
| 556 | 650 | ::ReleaseMutex(descriptor_mutex(*s_descriptor)); | |
| 557 | 650 | ::ReleaseSRWLockExclusive(&s_owner_lock); | |
| 558 | 738 | } | |
| 559 | |||
| 560 | 4140 | std::uint64_t active_xinput_raw_scopes() noexcept | |
| 561 | { | ||
| 562 | 4140 | return s_scope_state.load(std::memory_order_seq_cst) & SCOPE_COUNT_MASK; | |
| 563 | } | ||
| 564 | |||
| 565 | 18566 | bool xinput_consume_context_healthy() noexcept | |
| 566 | { | ||
| 567 | 18566 | if (s_cached_index.load(std::memory_order_acquire) == TLS_OUT_OF_INDEXES) | |
| 568 | { | ||
| 569 | ✗ | return false; | |
| 570 | } | ||
| 571 | 37132 | return ::InterlockedCompareExchange(&s_descriptor->consume_poison, 0, 0) == 0; | |
| 572 | } | ||
| 573 | |||
| 574 | 17392 | bool is_xinput_raw_call() noexcept | |
| 575 | { | ||
| 576 | 17347 | const DWORD index = s_cached_index.load(std::memory_order_acquire); | |
| 577 | 17347 | if (index == TLS_OUT_OF_INDEXES) | |
| 578 | { | ||
| 579 | ✗ | return false; | |
| 580 | } | ||
| 581 | 17347 | const LastErrorGuard preserve_error; | |
| 582 | 17300 | return reinterpret_cast<std::uintptr_t>(::TlsGetValue(index)) != 0; | |
| 583 | 17331 | } | |
| 584 | |||
| 585 | 2938 | XInputRawScope::XInputRawScope() noexcept | |
| 586 | { | ||
| 587 | 2938 | const LastErrorGuard preserve_error; | |
| 588 | 2938 | if (!admit_scope()) | |
| 589 | { | ||
| 590 | 2 | return; | |
| 591 | } | ||
| 592 | 2936 | m_index = s_cached_index.load(std::memory_order_acquire); | |
| 593 | 2936 | if (m_index == TLS_OUT_OF_INDEXES) | |
| 594 | { | ||
| 595 | s_scope_state.fetch_sub(1, std::memory_order_release); | ||
| 596 | ✗ | return; | |
| 597 | } | ||
| 598 | 2936 | m_previous_depth = reinterpret_cast<std::uintptr_t>(::TlsGetValue(m_index)); | |
| 599 | 5871 | if (::GetLastError() != ERROR_SUCCESS || m_previous_depth == std::numeric_limits<std::uintptr_t>::max() || | |
| 600 | 2935 | !store_depth(m_index, m_previous_depth + 1, true)) | |
| 601 | { | ||
| 602 | s_scope_state.fetch_sub(1, std::memory_order_release); | ||
| 603 | 4 | return; | |
| 604 | } | ||
| 605 | 2932 | m_admitted = true; | |
| 606 | 2932 | m_active = true; | |
| 607 | 2938 | } | |
| 608 | |||
| 609 | 2938 | XInputRawScope::~XInputRawScope() noexcept | |
| 610 | { | ||
| 611 | 2938 | (void)finish(); | |
| 612 | 2938 | } | |
| 613 | |||
| 614 | 5872 | bool XInputRawScope::finish() noexcept | |
| 615 | { | ||
| 616 | 5872 | if (m_active) | |
| 617 | { | ||
| 618 | 2932 | const LastErrorGuard preserve_error; | |
| 619 | 2932 | m_restored = store_depth(m_index, m_previous_depth, false); | |
| 620 | 2932 | m_active = false; | |
| 621 | s_scope_state.fetch_sub(1, std::memory_order_release); | ||
| 622 | 2932 | } | |
| 623 | 5872 | return m_restored; | |
| 624 | } | ||
| 625 | |||
| 626 | 7194 | XInputConsumeScope::XInputConsumeScope(DWORD user, const void *state_address) noexcept | |
| 627 | 7194 | : m_user(user), m_identity(reinterpret_cast<std::uintptr_t>(state_address)) | |
| 628 | { | ||
| 629 | 7194 | const LastErrorGuard preserve_error; | |
| 630 | 7194 | if (user > CONSUME_USER_MAX || m_identity == 0 || !admit_scope()) | |
| 631 | { | ||
| 632 | 8 | return; | |
| 633 | } | ||
| 634 | 7186 | if (!xinput_consume_context_healthy()) | |
| 635 | { | ||
| 636 | 9 | m_restored = false; | |
| 637 | s_scope_state.fetch_sub(1, std::memory_order_release); | ||
| 638 | 9 | return; | |
| 639 | } | ||
| 640 | 7177 | m_identity_index = s_cached_identity_index.load(std::memory_order_relaxed); | |
| 641 | 7177 | m_metadata_index = s_cached_metadata_index.load(std::memory_order_relaxed); | |
| 642 | 14354 | if (!read_consume(m_identity_index, m_previous_identity) || | |
| 643 | 7177 | !read_consume(m_metadata_index, m_previous_metadata)) | |
| 644 | { | ||
| 645 | ✗ | poison_consume_context(); | |
| 646 | ✗ | m_restored = false; | |
| 647 | s_scope_state.fetch_sub(1, std::memory_order_release); | ||
| 648 | ✗ | return; | |
| 649 | } | ||
| 650 | 7177 | const std::uint32_t previous_depth = consume_depth(m_previous_metadata); | |
| 651 | 7177 | m_joined = | |
| 652 | 7177 | previous_depth != 0 && m_identity == m_previous_identity && user == consume_user(m_previous_metadata); | |
| 653 | 7177 | if (m_joined && previous_depth == CONSUME_DEPTH_MASK) | |
| 654 | { | ||
| 655 | s_scope_state.fetch_sub(1, std::memory_order_release); | ||
| 656 | 1 | return; | |
| 657 | } | ||
| 658 | 7176 | m_depth = m_joined ? previous_depth + 1 : 1; | |
| 659 | 7176 | const std::uintptr_t metadata = m_joined ? m_previous_metadata + (std::uintptr_t{1} << CONSUME_DEPTH_SHIFT) | |
| 660 | 6937 | : (static_cast<std::uintptr_t>(user) << CONSUME_USER_SHIFT) | | |
| 661 | (std::uintptr_t{1} << CONSUME_DEPTH_SHIFT); | ||
| 662 | const bool identity_stored = | ||
| 663 | 7176 | m_joined || store_consume(m_identity_index, m_identity, ConsumeStore::EntryIdentity); | |
| 664 | const bool metadata_stored = | ||
| 665 | 7177 | identity_stored && store_consume(m_metadata_index, metadata, ConsumeStore::EntryMetadata); | |
| 666 | 7177 | if (!metadata_stored) | |
| 667 | { | ||
| 668 | 3 | poison_consume_context(); | |
| 669 | const bool identity_restored = | ||
| 670 | 3 | store_consume(m_identity_index, m_previous_identity, ConsumeStore::Rollback); | |
| 671 | const bool metadata_restored = | ||
| 672 | 3 | store_consume(m_metadata_index, m_previous_metadata, ConsumeStore::Rollback); | |
| 673 | 3 | m_restored = identity_restored && metadata_restored && xinput_consume_context_healthy(); | |
| 674 | s_scope_state.fetch_sub(1, std::memory_order_release); | ||
| 675 | 3 | return; | |
| 676 | } | ||
| 677 | 7174 | m_admitted = true; | |
| 678 | 7174 | m_active = true; | |
| 679 | 7195 | } | |
| 680 | |||
| 681 | 7194 | XInputConsumeScope::~XInputConsumeScope() noexcept | |
| 682 | { | ||
| 683 | 7194 | (void)finish(); | |
| 684 | 7194 | } | |
| 685 | |||
| 686 | 538 | bool XInputConsumeScope::original_buttons(WORD observed, WORD &original) noexcept | |
| 687 | { | ||
| 688 | 538 | const LastErrorGuard preserve_error; | |
| 689 | 538 | if (!m_active || !xinput_consume_context_healthy()) | |
| 690 | { | ||
| 691 | 5 | return false; | |
| 692 | } | ||
| 693 | 533 | std::uintptr_t identity = 0; | |
| 694 | 533 | std::uintptr_t metadata = 0; | |
| 695 | 1066 | if (!read_consume(m_identity_index, identity) || !read_consume(m_metadata_index, metadata) || | |
| 696 | 1066 | identity != m_identity || consume_user(metadata) != m_user || consume_depth(metadata) != m_depth) | |
| 697 | { | ||
| 698 | ✗ | poison_consume_context(); | |
| 699 | ✗ | return false; | |
| 700 | } | ||
| 701 | 533 | const auto expected = static_cast<WORD>((metadata & CONSUME_EXPECTED_MASK) >> CONSUME_EXPECTED_SHIFT); | |
| 702 | 533 | if ((metadata & CONSUME_VALID) == 0 || observed != expected) | |
| 703 | { | ||
| 704 | 294 | metadata = (metadata & ~(CONSUME_BUTTONS_MASK | CONSUME_EXPECTED_MASK)) | observed | | |
| 705 | 294 | (static_cast<std::uintptr_t>(observed) << CONSUME_EXPECTED_SHIFT) | CONSUME_VALID; | |
| 706 | 294 | if (!store_consume(m_metadata_index, metadata, ConsumeStore::Original)) | |
| 707 | { | ||
| 708 | 3 | poison_consume_context(); | |
| 709 | 3 | return false; | |
| 710 | } | ||
| 711 | } | ||
| 712 | 530 | if (!xinput_consume_context_healthy()) | |
| 713 | { | ||
| 714 | ✗ | return false; | |
| 715 | } | ||
| 716 | 530 | original = static_cast<WORD>(metadata & CONSUME_BUTTONS_MASK); | |
| 717 | 530 | return true; | |
| 718 | 538 | } | |
| 719 | |||
| 720 | 13881 | bool XInputConsumeScope::finish() noexcept | |
| 721 | { | ||
| 722 | 13881 | return finish_context(false, 0); | |
| 723 | } | ||
| 724 | |||
| 725 | 502 | bool XInputConsumeScope::finish(WORD returned_buttons) noexcept | |
| 726 | { | ||
| 727 | 502 | return finish_context(true, returned_buttons); | |
| 728 | } | ||
| 729 | |||
| 730 | 14383 | bool XInputConsumeScope::finish_context(bool publish_return, WORD returned_buttons) noexcept | |
| 731 | { | ||
| 732 | 14383 | if (m_active) | |
| 733 | { | ||
| 734 | 7173 | const LastErrorGuard preserve_error; | |
| 735 | 7173 | bool identity_restored = true; | |
| 736 | 7173 | bool metadata_restored = false; | |
| 737 | 7173 | if (m_joined) | |
| 738 | { | ||
| 739 | 239 | std::uintptr_t identity = 0; | |
| 740 | 239 | std::uintptr_t metadata = 0; | |
| 741 | 478 | if (read_consume(m_identity_index, identity) && read_consume(m_metadata_index, metadata) && | |
| 742 | 717 | identity == m_identity && consume_user(metadata) == m_user && | |
| 743 | 239 | consume_depth(metadata) == m_depth) | |
| 744 | { | ||
| 745 | 239 | if (publish_return) | |
| 746 | { | ||
| 747 | 239 | metadata = (metadata & ~CONSUME_EXPECTED_MASK) | | |
| 748 | 239 | (static_cast<std::uintptr_t>(returned_buttons) << CONSUME_EXPECTED_SHIFT); | |
| 749 | } | ||
| 750 | 239 | metadata_restored = store_consume( | |
| 751 | m_metadata_index, | ||
| 752 | metadata - (std::uintptr_t{1} << CONSUME_DEPTH_SHIFT), | ||
| 753 | ConsumeStore::RestoreMetadata | ||
| 754 | ); | ||
| 755 | } | ||
| 756 | } | ||
| 757 | else | ||
| 758 | { | ||
| 759 | identity_restored = | ||
| 760 | 6934 | store_consume(m_identity_index, m_previous_identity, ConsumeStore::RestoreIdentity); | |
| 761 | 6934 | if (!identity_restored) | |
| 762 | { | ||
| 763 | 2 | poison_consume_context(); | |
| 764 | } | ||
| 765 | metadata_restored = | ||
| 766 | 6934 | store_consume(m_metadata_index, m_previous_metadata, ConsumeStore::RestoreMetadata); | |
| 767 | } | ||
| 768 | 7173 | if (!identity_restored || !metadata_restored) | |
| 769 | { | ||
| 770 | 4 | poison_consume_context(); | |
| 771 | } | ||
| 772 | 7173 | m_restored = identity_restored && metadata_restored && xinput_consume_context_healthy(); | |
| 773 | 7173 | m_active = false; | |
| 774 | s_scope_state.fetch_sub(1, std::memory_order_release); | ||
| 775 | 7173 | } | |
| 776 | 14383 | return m_restored; | |
| 777 | } | ||
| 778 | |||
| 779 | #if defined(DMK_ENABLE_TEST_SEAMS) | ||
| 780 | 40 | bool set_xinput_raw_scope_failure_for_test(XInputRawScopeFailure failure) noexcept | |
| 781 | { | ||
| 782 | 40 | const std::uint64_t thread = ::GetCurrentThreadId(); | |
| 783 | 200 | for (auto &slot : s_failures) | |
| 784 | { | ||
| 785 | 180 | std::uint64_t current = slot.load(std::memory_order_acquire); | |
| 786 | 200 | if ((current >> 8) == thread && | |
| 787 | 40 | slot.compare_exchange_strong( | |
| 788 | current, | ||
| 789 | ✗ | failure == XInputRawScopeFailure::None ? 0 : (thread << 8) | static_cast<std::uint8_t>(failure), | |
| 790 | std::memory_order_acq_rel, | ||
| 791 | std::memory_order_acquire | ||
| 792 | )) | ||
| 793 | { | ||
| 794 | 20 | return true; | |
| 795 | } | ||
| 796 | } | ||
| 797 | 20 | if (failure == XInputRawScopeFailure::None) | |
| 798 | { | ||
| 799 | ✗ | return true; | |
| 800 | } | ||
| 801 | 20 | for (auto &slot : s_failures) | |
| 802 | { | ||
| 803 | 20 | std::uint64_t empty = 0; | |
| 804 | 20 | if (slot.compare_exchange_strong( | |
| 805 | empty, | ||
| 806 | 20 | (thread << 8) | static_cast<std::uint8_t>(failure), | |
| 807 | std::memory_order_acq_rel, | ||
| 808 | std::memory_order_acquire | ||
| 809 | )) | ||
| 810 | { | ||
| 811 | 20 | return true; | |
| 812 | } | ||
| 813 | } | ||
| 814 | ✗ | return false; | |
| 815 | } | ||
| 816 | |||
| 817 | 52 | DWORD xinput_raw_scope_index_for_test() noexcept | |
| 818 | { | ||
| 819 | 52 | return s_cached_index.load(std::memory_order_acquire); | |
| 820 | } | ||
| 821 | |||
| 822 | 8 | std::uint32_t xinput_raw_scope_owners_for_test() noexcept | |
| 823 | { | ||
| 824 | 8 | const LastErrorGuard preserve_error; | |
| 825 | 8 | ::AcquireSRWLockExclusive(&s_owner_lock); | |
| 826 | 8 | DWORD error = ERROR_SUCCESS; | |
| 827 | 8 | std::uint32_t owners = 0; | |
| 828 | 8 | if (s_descriptor != nullptr && lock_descriptor(*s_descriptor, error)) | |
| 829 | { | ||
| 830 | 8 | owners = s_descriptor->owners; | |
| 831 | 8 | ::ReleaseMutex(descriptor_mutex(*s_descriptor)); | |
| 832 | } | ||
| 833 | 8 | ::ReleaseSRWLockExclusive(&s_owner_lock); | |
| 834 | 16 | return owners; | |
| 835 | 8 | } | |
| 836 | |||
| 837 | 7 | std::uintptr_t xinput_raw_scope_depth_for_test() noexcept | |
| 838 | { | ||
| 839 | 7 | const DWORD index = s_cached_index.load(std::memory_order_acquire); | |
| 840 | 7 | if (index == TLS_OUT_OF_INDEXES) | |
| 841 | { | ||
| 842 | ✗ | return 0; | |
| 843 | } | ||
| 844 | 7 | const LastErrorGuard preserve_error; | |
| 845 | 7 | return reinterpret_cast<std::uintptr_t>(::TlsGetValue(index)); | |
| 846 | 7 | } | |
| 847 | |||
| 848 | 8 | std::uintptr_t xinput_raw_scope_descriptor_for_test() noexcept | |
| 849 | { | ||
| 850 | 8 | ::AcquireSRWLockExclusive(&s_owner_lock); | |
| 851 | 8 | const auto address = reinterpret_cast<std::uintptr_t>(s_descriptor); | |
| 852 | 8 | ::ReleaseSRWLockExclusive(&s_owner_lock); | |
| 853 | 8 | return address; | |
| 854 | } | ||
| 855 | |||
| 856 | 13 | DWORD xinput_consume_identity_index_for_test() noexcept | |
| 857 | { | ||
| 858 | 13 | return s_cached_identity_index.load(std::memory_order_relaxed); | |
| 859 | } | ||
| 860 | |||
| 861 | 13 | DWORD xinput_consume_metadata_index_for_test() noexcept | |
| 862 | { | ||
| 863 | 13 | return s_cached_metadata_index.load(std::memory_order_relaxed); | |
| 864 | } | ||
| 865 | #endif | ||
| 866 | } // namespace detail | ||
| 867 | } // namespace DetourModKit | ||
| 868 |