./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
2 / 2
100.0%
Branches:
-
-%
Functions:
2 / 2
100.0%

src/internal/xinput_raw_scope.hpp
Line Branch Exec Source
1 #ifndef DETOURMODKIT_INTERNAL_XINPUT_RAW_SCOPE_HPP
2 #define DETOURMODKIT_INTERNAL_XINPUT_RAW_SCOPE_HPP
3
4 #include <windows.h>
5
6 #include <cstdint>
7
8 namespace DetourModKit::detail
9 {
10 /**
11 * @brief Reserves shared XInput scope TLS for one local owner.
12 * @param error Receives the platform error after failure, when supplied.
13 * @return False when the descriptor or TLS index is unavailable.
14 * @note Setup/control-plane only. Each successful call requires one release after callbacks and samples drain.
15 * @note Lock order: local owner SRW lock, then process descriptor mutex.
16 */
17 [[nodiscard]] bool acquire_xinput_raw_scope_owner(DWORD *error = nullptr) noexcept;
18
19 /**
20 * @brief Releases one owner after callbacks and samples drain.
21 * @note Setup/control-plane only. Retained routes must retain their owner.
22 */
23 void release_xinput_raw_scope_owner() noexcept;
24
25 /** @brief Returns the number of local raw and consume scopes that remain active. */
26 [[nodiscard]] std::uint64_t active_xinput_raw_scopes() noexcept;
27
28 /**
29 * @brief Returns whether the live shared consume context remains healthy.
30 * @note Callback-safe. A context fault disables every compatible copy until all owners retire.
31 */
32 [[nodiscard]] bool xinput_consume_context_healthy() noexcept;
33
34 /**
35 * @brief Returns whether this thread has a raw scope from a compatible copy.
36 * @note Callback-safe. The query preserves the caller's last-error value and takes no application lock.
37 */
38 [[nodiscard]] bool is_xinput_raw_call() noexcept;
39
40 /**
41 * @brief Marks a nested raw sample without a C++ allocation or application lock.
42 * @note Refuse the sample after failed admission. Disarm its consume cycle after a failed finish.
43 */
44 class XInputRawScope
45 {
46 public:
47 /** @brief Attempts raw-sample admission and preserves the caller's last-error value. */
48 XInputRawScope() noexcept;
49 /** @brief Restores the previous depth if finish did not already restore it. */
50 ~XInputRawScope() noexcept;
51
52 XInputRawScope(const XInputRawScope &) = delete;
53 XInputRawScope &operator=(const XInputRawScope &) = delete;
54 XInputRawScope(XInputRawScope &&) = delete;
55 XInputRawScope &operator=(XInputRawScope &&) = delete;
56
57 /** @brief Returns whether the entry store succeeded. */
58 4211 [[nodiscard]] bool admitted() const noexcept { return m_admitted; }
59
60 /**
61 * @brief Restores the previous depth and preserves the provider's last-error value.
62 * @return False after a restore failure. Repeated calls retain the first result.
63 */
64 [[nodiscard]] bool finish() noexcept;
65
66 private:
67 DWORD m_index{TLS_OUT_OF_INDEXES};
68 std::uintptr_t m_previous_depth{0};
69 bool m_admitted{false};
70 bool m_active{false};
71 bool m_restored{true};
72 };
73
74 /**
75 * @brief Shares original digital buttons across compatible detours for one state call.
76 * @note Callback-safe. Finish before a local mask. The state address serves only as a numeric identity.
77 */
78 class XInputConsumeScope
79 {
80 public:
81 /** @brief Attempts admission for users 0 through 3 and the numeric state identity. */
82 XInputConsumeScope(DWORD user, const void *state_address) noexcept;
83 /** @brief Restores the prior context if finish did not already restore it. */
84 ~XInputConsumeScope() noexcept;
85
86 XInputConsumeScope(const XInputConsumeScope &) = delete;
87 XInputConsumeScope &operator=(const XInputConsumeScope &) = delete;
88 XInputConsumeScope(XInputConsumeScope &&) = delete;
89 XInputConsumeScope &operator=(XInputConsumeScope &&) = delete;
90
91 /** @brief Returns whether the entry stores succeeded. */
92 544 [[nodiscard]] bool admitted() const noexcept { return m_admitted; }
93
94 /**
95 * @brief Returns shared original buttons or reseeds them after a visible upstream button change.
96 * @return False after a context fault. The output remains unchanged after failure.
97 */
98 [[nodiscard]] bool original_buttons(WORD observed, WORD &original) noexcept;
99
100 /**
101 * @brief Restores the prior context and preserves the provider's last-error value.
102 * @return False after a context fault. Repeated calls retain the first result.
103 * @note The expected buttons from the lower compatible detour remain unchanged.
104 */
105 [[nodiscard]] bool finish() noexcept;
106
107 /**
108 * @brief Publishes the proposed return buttons before the prior context resumes.
109 * @return False after a context fault. Commit the proposed buttons only after success.
110 */
111 [[nodiscard]] bool finish(WORD returned_buttons) noexcept;
112
113 private:
114 [[nodiscard]] bool finish_context(bool publish_return, WORD returned_buttons) noexcept;
115
116 DWORD m_identity_index{TLS_OUT_OF_INDEXES};
117 DWORD m_metadata_index{TLS_OUT_OF_INDEXES};
118 DWORD m_user{0};
119 std::uintptr_t m_identity{0};
120 std::uintptr_t m_previous_identity{0};
121 std::uintptr_t m_previous_metadata{0};
122 std::uint32_t m_depth{0};
123 bool m_joined{false};
124 bool m_admitted{false};
125 bool m_active{false};
126 bool m_restored{true};
127 };
128
129 #if defined(DMK_ENABLE_TEST_SEAMS)
130 /** @brief Selects one deterministic platform refusal on the calling thread. */
131 enum class XInputRawScopeFailure : std::uint8_t
132 {
133 None,
134 Reservation,
135 EntryStore,
136 Restore,
137 DescriptorInit,
138 DescriptorVersion,
139 IdentityReservation,
140 MetadataReservation,
141 ConsumeEntryIdentity,
142 ConsumeEntryMetadata,
143 ConsumeEntryRollback,
144 ConsumeOriginalStore,
145 ConsumeRestoreIdentity,
146 ConsumeRestoreMetadata,
147 };
148
149 /** @brief Sets one refusal for the calling thread. None clears that thread's refusal. */
150 [[nodiscard]] bool set_xinput_raw_scope_failure_for_test(XInputRawScopeFailure failure) noexcept;
151 /** @brief Returns the cached TLS index or TLS_OUT_OF_INDEXES. */
152 [[nodiscard]] DWORD xinput_raw_scope_index_for_test() noexcept;
153 /** @brief Returns the owner count in the shared descriptor. */
154 [[nodiscard]] std::uint32_t xinput_raw_scope_owners_for_test() noexcept;
155 /** @brief Returns this thread's raw depth without a last-error change. */
156 [[nodiscard]] std::uintptr_t xinput_raw_scope_depth_for_test() noexcept;
157 /** @brief Returns the shared descriptor address. */
158 [[nodiscard]] std::uintptr_t xinput_raw_scope_descriptor_for_test() noexcept;
159 /** @brief Returns the cached consume identity index or TLS_OUT_OF_INDEXES. */
160 [[nodiscard]] DWORD xinput_consume_identity_index_for_test() noexcept;
161 /** @brief Returns the cached consume metadata index or TLS_OUT_OF_INDEXES. */
162 [[nodiscard]] DWORD xinput_consume_metadata_index_for_test() noexcept;
163 #endif
164 } // namespace DetourModKit::detail
165
166 #endif // DETOURMODKIT_INTERNAL_XINPUT_RAW_SCOPE_HPP
167