GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 97.8% 87 / 0 / 89
Functions: 100.0% 11 / 0 / 11
Branches: 74.0% 71 / 0 / 96

src/input_codes.cpp
Line Branch Exec Source
1 /**
2 * @file input_codes.cpp
3 * @brief Implementation of named input code resolution and formatting.
4 *
5 * Provides the name lookup table and resolution functions for converting between human-readable input names (e.g.,
6 * "Ctrl", "Mouse4", "Gamepad_A") and their corresponding InputCode values.
7 */
8
9 #include "DetourModKit/input_codes.hpp"
10 #include "DetourModKit/format.hpp"
11
12 #include <algorithm>
13 #include <charconv>
14 #include <limits>
15 #include <unordered_map>
16
17 namespace DetourModKit
18 {
19 namespace
20 {
21 struct NameEntry
22 {
23 const char *name = nullptr;
24 InputCode code;
25 };
26
27 // clang-format off
28 constexpr NameEntry NAME_TABLE[] = {
29 // Keyboard: Modifiers
30 {"Ctrl", {InputSource::Keyboard, 0x11}},
31 {"LCtrl", {InputSource::Keyboard, 0xA2}},
32 {"RCtrl", {InputSource::Keyboard, 0xA3}},
33 {"Shift", {InputSource::Keyboard, 0x10}},
34 {"LShift", {InputSource::Keyboard, 0xA0}},
35 {"RShift", {InputSource::Keyboard, 0xA1}},
36 {"Alt", {InputSource::Keyboard, 0x12}},
37 {"LAlt", {InputSource::Keyboard, 0xA4}},
38 {"RAlt", {InputSource::Keyboard, 0xA5}},
39
40 // Keyboard: Function keys
41 {"F1", {InputSource::Keyboard, 0x70}},
42 {"F2", {InputSource::Keyboard, 0x71}},
43 {"F3", {InputSource::Keyboard, 0x72}},
44 {"F4", {InputSource::Keyboard, 0x73}},
45 {"F5", {InputSource::Keyboard, 0x74}},
46 {"F6", {InputSource::Keyboard, 0x75}},
47 {"F7", {InputSource::Keyboard, 0x76}},
48 {"F8", {InputSource::Keyboard, 0x77}},
49 {"F9", {InputSource::Keyboard, 0x78}},
50 {"F10", {InputSource::Keyboard, 0x79}},
51 {"F11", {InputSource::Keyboard, 0x7A}},
52 {"F12", {InputSource::Keyboard, 0x7B}},
53 {"F13", {InputSource::Keyboard, 0x7C}},
54 {"F14", {InputSource::Keyboard, 0x7D}},
55 {"F15", {InputSource::Keyboard, 0x7E}},
56 {"F16", {InputSource::Keyboard, 0x7F}},
57 {"F17", {InputSource::Keyboard, 0x80}},
58 {"F18", {InputSource::Keyboard, 0x81}},
59 {"F19", {InputSource::Keyboard, 0x82}},
60 {"F20", {InputSource::Keyboard, 0x83}},
61 {"F21", {InputSource::Keyboard, 0x84}},
62 {"F22", {InputSource::Keyboard, 0x85}},
63 {"F23", {InputSource::Keyboard, 0x86}},
64 {"F24", {InputSource::Keyboard, 0x87}},
65
66 // Keyboard: Letters
67 {"A", {InputSource::Keyboard, 0x41}},
68 {"B", {InputSource::Keyboard, 0x42}},
69 {"C", {InputSource::Keyboard, 0x43}},
70 {"D", {InputSource::Keyboard, 0x44}},
71 {"E", {InputSource::Keyboard, 0x45}},
72 {"F", {InputSource::Keyboard, 0x46}},
73 {"G", {InputSource::Keyboard, 0x47}},
74 {"H", {InputSource::Keyboard, 0x48}},
75 {"I", {InputSource::Keyboard, 0x49}},
76 {"J", {InputSource::Keyboard, 0x4A}},
77 {"K", {InputSource::Keyboard, 0x4B}},
78 {"L", {InputSource::Keyboard, 0x4C}},
79 {"M", {InputSource::Keyboard, 0x4D}},
80 {"N", {InputSource::Keyboard, 0x4E}},
81 {"O", {InputSource::Keyboard, 0x4F}},
82 {"P", {InputSource::Keyboard, 0x50}},
83 {"Q", {InputSource::Keyboard, 0x51}},
84 {"R", {InputSource::Keyboard, 0x52}},
85 {"S", {InputSource::Keyboard, 0x53}},
86 {"T", {InputSource::Keyboard, 0x54}},
87 {"U", {InputSource::Keyboard, 0x55}},
88 {"V", {InputSource::Keyboard, 0x56}},
89 {"W", {InputSource::Keyboard, 0x57}},
90 {"X", {InputSource::Keyboard, 0x58}},
91 {"Y", {InputSource::Keyboard, 0x59}},
92 {"Z", {InputSource::Keyboard, 0x5A}},
93
94 // Keyboard: Digits
95 {"0", {InputSource::Keyboard, 0x30}},
96 {"1", {InputSource::Keyboard, 0x31}},
97 {"2", {InputSource::Keyboard, 0x32}},
98 {"3", {InputSource::Keyboard, 0x33}},
99 {"4", {InputSource::Keyboard, 0x34}},
100 {"5", {InputSource::Keyboard, 0x35}},
101 {"6", {InputSource::Keyboard, 0x36}},
102 {"7", {InputSource::Keyboard, 0x37}},
103 {"8", {InputSource::Keyboard, 0x38}},
104 {"9", {InputSource::Keyboard, 0x39}},
105
106 // Keyboard: Navigation
107 {"Up", {InputSource::Keyboard, 0x26}},
108 {"Down", {InputSource::Keyboard, 0x28}},
109 {"Left", {InputSource::Keyboard, 0x25}},
110 {"Right", {InputSource::Keyboard, 0x27}},
111 {"Home", {InputSource::Keyboard, 0x24}},
112 {"End", {InputSource::Keyboard, 0x23}},
113 {"PageUp", {InputSource::Keyboard, 0x21}},
114 {"PageDown", {InputSource::Keyboard, 0x22}},
115 {"Insert", {InputSource::Keyboard, 0x2D}},
116 {"Delete", {InputSource::Keyboard, 0x2E}},
117
118 // Keyboard: Common keys
119 {"Space", {InputSource::Keyboard, 0x20}},
120 {"Enter", {InputSource::Keyboard, 0x0D}},
121 {"Escape", {InputSource::Keyboard, 0x1B}},
122 {"Tab", {InputSource::Keyboard, 0x09}},
123 {"Backspace", {InputSource::Keyboard, 0x08}},
124 {"CapsLock", {InputSource::Keyboard, 0x14}},
125 {"NumLock", {InputSource::Keyboard, 0x90}},
126 {"ScrollLock", {InputSource::Keyboard, 0x91}},
127 {"PrintScreen", {InputSource::Keyboard, 0x2C}},
128 {"Pause", {InputSource::Keyboard, 0x13}},
129
130 // Keyboard: Windows / application keys
131 {"LWin", {InputSource::Keyboard, 0x5B}},
132 {"RWin", {InputSource::Keyboard, 0x5C}},
133 {"Apps", {InputSource::Keyboard, 0x5D}},
134 {"Menu", {InputSource::Keyboard, 0x5D}}, // alias for the application/context-menu key
135
136 // Keyboard: OEM punctuation (US layout)
137 // The canonical name is listed first so the reverse lookup picks it; trailing aliases still parse. Grave
138 // (VK_OEM_3) is the backtick/tilde key many games use to open the console.
139 {"Semicolon", {InputSource::Keyboard, 0xBA}},
140 {"Equals", {InputSource::Keyboard, 0xBB}},
141 {"Comma", {InputSource::Keyboard, 0xBC}},
142 {"Minus", {InputSource::Keyboard, 0xBD}},
143 {"Period", {InputSource::Keyboard, 0xBE}},
144 {"Slash", {InputSource::Keyboard, 0xBF}},
145 {"Grave", {InputSource::Keyboard, 0xC0}},
146 {"Backtick", {InputSource::Keyboard, 0xC0}}, // alias for Grave
147 {"Tilde", {InputSource::Keyboard, 0xC0}}, // alias for Grave
148 {"LBracket", {InputSource::Keyboard, 0xDB}},
149 {"Backslash", {InputSource::Keyboard, 0xDC}},
150 {"RBracket", {InputSource::Keyboard, 0xDD}},
151 {"Apostrophe", {InputSource::Keyboard, 0xDE}},
152 {"Quote", {InputSource::Keyboard, 0xDE}}, // alias for Apostrophe
153
154 // Keyboard: Numpad
155 {"Numpad0", {InputSource::Keyboard, 0x60}},
156 {"Numpad1", {InputSource::Keyboard, 0x61}},
157 {"Numpad2", {InputSource::Keyboard, 0x62}},
158 {"Numpad3", {InputSource::Keyboard, 0x63}},
159 {"Numpad4", {InputSource::Keyboard, 0x64}},
160 {"Numpad5", {InputSource::Keyboard, 0x65}},
161 {"Numpad6", {InputSource::Keyboard, 0x66}},
162 {"Numpad7", {InputSource::Keyboard, 0x67}},
163 {"Numpad8", {InputSource::Keyboard, 0x68}},
164 {"Numpad9", {InputSource::Keyboard, 0x69}},
165 {"NumpadAdd", {InputSource::Keyboard, 0x6B}},
166 {"NumpadSubtract", {InputSource::Keyboard, 0x6D}},
167 {"NumpadMultiply", {InputSource::Keyboard, 0x6A}},
168 {"NumpadDivide", {InputSource::Keyboard, 0x6F}},
169 {"NumpadDecimal", {InputSource::Keyboard, 0x6E}},
170
171 // Mouse buttons
172 {"Mouse1", {InputSource::Mouse, 0x01}},
173 {"Mouse2", {InputSource::Mouse, 0x02}},
174 {"Mouse3", {InputSource::Mouse, 0x04}},
175 {"Mouse4", {InputSource::Mouse, 0x05}},
176 {"Mouse5", {InputSource::Mouse, 0x06}},
177
178 // Mouse wheel (event-only; captured via the input layer's queue message hook)
179 {"WheelUp", {InputSource::MouseWheel, WheelCode::Up}},
180 {"WheelDown", {InputSource::MouseWheel, WheelCode::Down}},
181 {"WheelLeft", {InputSource::MouseWheel, WheelCode::Left}},
182 {"WheelRight", {InputSource::MouseWheel, WheelCode::Right}},
183
184 // Gamepad buttons
185 {"Gamepad_A", {InputSource::Gamepad, GamepadCode::A}},
186 {"Gamepad_B", {InputSource::Gamepad, GamepadCode::B}},
187 {"Gamepad_X", {InputSource::Gamepad, GamepadCode::X}},
188 {"Gamepad_Y", {InputSource::Gamepad, GamepadCode::Y}},
189 {"Gamepad_LB", {InputSource::Gamepad, GamepadCode::LeftBumper}},
190 {"Gamepad_RB", {InputSource::Gamepad, GamepadCode::RightBumper}},
191 {"Gamepad_LT", {InputSource::Gamepad, GamepadCode::LeftTrigger}},
192 {"Gamepad_RT", {InputSource::Gamepad, GamepadCode::RightTrigger}},
193 {"Gamepad_Start", {InputSource::Gamepad, GamepadCode::Start}},
194 {"Gamepad_Back", {InputSource::Gamepad, GamepadCode::Back}},
195 {"Gamepad_LS", {InputSource::Gamepad, GamepadCode::LeftStick}},
196 {"Gamepad_RS", {InputSource::Gamepad, GamepadCode::RightStick}},
197 {"Gamepad_DpadUp", {InputSource::Gamepad, GamepadCode::DpadUp}},
198 {"Gamepad_DpadDown", {InputSource::Gamepad, GamepadCode::DpadDown}},
199 {"Gamepad_DpadLeft", {InputSource::Gamepad, GamepadCode::DpadLeft}},
200 {"Gamepad_DpadRight", {InputSource::Gamepad, GamepadCode::DpadRight}},
201
202 // Gamepad thumbstick axes (digital)
203 {"Gamepad_LSUp", {InputSource::Gamepad, GamepadCode::LeftStickUp}},
204 {"Gamepad_LSDown", {InputSource::Gamepad, GamepadCode::LeftStickDown}},
205 {"Gamepad_LSLeft", {InputSource::Gamepad, GamepadCode::LeftStickLeft}},
206 {"Gamepad_LSRight", {InputSource::Gamepad, GamepadCode::LeftStickRight}},
207 {"Gamepad_RSUp", {InputSource::Gamepad, GamepadCode::RightStickUp}},
208 {"Gamepad_RSDown", {InputSource::Gamepad, GamepadCode::RightStickDown}},
209 {"Gamepad_RSLeft", {InputSource::Gamepad, GamepadCode::RightStickLeft}},
210 {"Gamepad_RSRight", {InputSource::Gamepad, GamepadCode::RightStickRight}},
211 };
212 // clang-format on
213
214 constexpr size_t NAME_TABLE_SIZE = sizeof(NAME_TABLE) / sizeof(NAME_TABLE[0]);
215
216 113192 int icompare(std::string_view a, std::string_view b) noexcept
217 {
218 // Branch-free ASCII case fold. The input-name and source tables are pure ASCII, so a locale-sensitive
219 // std::tolower is both unnecessary and a portability hazard on this resolution path: under a non-invariant
220 // CRT locale it could fold identifiers differently (the ASCII 'I'/'i' pair is the classic Turkish example),
221 // making name resolution locale-dependent. Folding only 'A'-'Z' inline keeps the comparison deterministic
222 // across every locale and allocation-free.
223 518346 const auto ascii_lower = [](char c) noexcept -> unsigned char
224 {
225 518346 const auto u = static_cast<unsigned char>(c);
226
4/4
✓ Branch 2 → 3 taken 466314 times.
✓ Branch 2 → 5 taken 52032 times.
✓ Branch 3 → 4 taken 255666 times.
✓ Branch 3 → 5 taken 210648 times.
518346 return (u >= 'A' && u <= 'Z') ? static_cast<unsigned char>(u + ('a' - 'A')) : u;
227 };
228 113192 const size_t len = std::min(a.size(), b.size());
229
2/2
✓ Branch 13 → 6 taken 259173 times.
✓ Branch 13 → 14 taken 7464 times.
266637 for (size_t i = 0; i < len; ++i)
230 {
231 259173 const int ca = ascii_lower(a[i]);
232 259173 const int cb = ascii_lower(b[i]);
233
2/2
✓ Branch 10 → 11 taken 105728 times.
✓ Branch 10 → 12 taken 153445 times.
259173 if (ca != cb)
234 {
235 105728 return ca - cb;
236 }
237 }
238
2/2
✓ Branch 16 → 17 taken 5246 times.
✓ Branch 16 → 18 taken 2218 times.
7464 if (a.size() < b.size())
239 {
240 5246 return -1;
241 }
242
2/2
✓ Branch 20 → 21 taken 2043 times.
✓ Branch 20 → 22 taken 175 times.
2218 if (a.size() > b.size())
243 {
244 2043 return 1;
245 }
246 175 return 0;
247 }
248
249 /**
250 * @brief Parses a source-tagged hex token such as "Mouse:0xFE" into an InputCode.
251 * @details The inverse of format_input_code's off-table form for non-keyboard sources. The source tag matches
252 * input_source_to_string ("Keyboard", "Mouse", "Gamepad", "MouseWheel"), case-insensitively, and the
253 * value parses as hex with or without a 0x prefix.
254 * @param source_token The device-source tag (text before the ':').
255 * @param hex_token The hex value (text after the ':'), with or without a 0x/0X prefix.
256 * @return The resolved InputCode, or std::nullopt if the tag is unknown or the value is not a valid
257 * non-negative hex int.
258 */
259 std::optional<InputCode>
260 10 parse_source_tagged_hex(std::string_view source_token, std::string_view hex_token) noexcept
261 {
262 10 InputSource source = InputSource::Keyboard;
263
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 10 times.
10 if (icompare(source_token, "Keyboard") == 0)
264 {
265 source = InputSource::Keyboard;
266 }
267
2/2
✓ Branch 8 → 9 taken 5 times.
✓ Branch 8 → 10 taken 5 times.
10 else if (icompare(source_token, "Mouse") == 0)
268 {
269 5 source = InputSource::Mouse;
270 }
271
2/2
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 14 taken 3 times.
5 else if (icompare(source_token, "Gamepad") == 0)
272 {
273 2 source = InputSource::Gamepad;
274 }
275
2/2
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 18 taken 1 time.
3 else if (icompare(source_token, "MouseWheel") == 0)
276 {
277 2 source = InputSource::MouseWheel;
278 }
279 else
280 {
281 1 return std::nullopt;
282 }
283
284 // Strip an optional 0x / 0X prefix; std::from_chars(base 16) does not accept one.
285
7/10
✓ Branch 20 → 21 taken 8 times.
✓ Branch 20 → 28 taken 1 time.
✓ Branch 22 → 23 taken 7 times.
✓ Branch 22 → 28 taken 1 time.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 27 taken 7 times.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
✓ Branch 29 → 30 taken 7 times.
✓ Branch 29 → 31 taken 2 times.
9 if (hex_token.size() >= 2 && hex_token[0] == '0' && (hex_token[1] == 'x' || hex_token[1] == 'X'))
286 {
287 7 hex_token.remove_prefix(2);
288 }
289
2/2
✓ Branch 32 → 33 taken 1 time.
✓ Branch 32 → 34 taken 8 times.
9 if (hex_token.empty())
290 {
291 1 return std::nullopt;
292 }
293
294 8 unsigned long value = 0;
295 8 const char *const first = hex_token.data();
296 8 const char *const last = hex_token.data() + hex_token.size();
297 8 const auto [ptr, ec] = std::from_chars(first, last, value, 16);
298
3/4
✓ Branch 38 → 39 taken 7 times.
✓ Branch 38 → 40 taken 1 time.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 7 times.
8 if (ec != std::errc{} || ptr != last)
299 {
300 1 return std::nullopt;
301 }
302
1/2
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 44 taken 7 times.
7 if (value > static_cast<unsigned long>(std::numeric_limits<int>::max()))
303 {
304 return std::nullopt;
305 }
306 7 return InputCode{source, static_cast<int>(value)};
307 }
308
309 struct SortedNameIndex
310 {
311 size_t indices[NAME_TABLE_SIZE]{};
312 size_t count = NAME_TABLE_SIZE;
313
314 88 SortedNameIndex()
315 88 {
316
2/2
✓ Branch 4 → 3 taken 13640 times.
✓ Branch 4 → 5 taken 88 times.
13728 for (size_t i = 0; i < NAME_TABLE_SIZE; ++i)
317 {
318 13640 indices[i] = i;
319 }
320 88 std::sort(
321 88 indices,
322 88 indices + count,
323 111672 [](size_t a, size_t b) { return icompare(NAME_TABLE[a].name, NAME_TABLE[b].name) < 0; }
324 );
325 88 }
326 };
327
328 222 const SortedNameIndex &get_sorted_index()
329 {
330
4/8
✓ Branch 2 → 3 taken 88 times.
✓ Branch 2 → 7 taken 134 times.
✓ Branch 4 → 5 taken 88 times.
✗ Branch 4 → 7 not taken.
✓ Branch 5 → 6 taken 88 times.
✗ Branch 5 → 9 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
222 static const SortedNameIndex instance;
331 222 return instance;
332 }
333
334 struct CodeNameMap
335 {
336 std::unordered_map<InputCode, std::string_view, InputCodeHash> map;
337
338 17 CodeNameMap()
339 17 {
340
1/2
✓ Branch 3 → 4 taken 17 times.
✗ Branch 3 → 10 not taken.
17 map.reserve(NAME_TABLE_SIZE);
341
2/2
✓ Branch 7 → 5 taken 2635 times.
✓ Branch 7 → 8 taken 17 times.
2652 for (size_t i = 0; i < NAME_TABLE_SIZE; ++i)
342 {
343
1/2
✓ Branch 5 → 6 taken 2635 times.
✗ Branch 5 → 9 not taken.
2635 map.emplace(NAME_TABLE[i].code, NAME_TABLE[i].name);
344 }
345 17 }
346 };
347
348 35 const CodeNameMap &get_code_name_map()
349 {
350
4/8
✓ Branch 2 → 3 taken 17 times.
✓ Branch 2 → 8 taken 18 times.
✓ Branch 4 → 5 taken 17 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 17 times.
✗ Branch 5 → 10 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
35 static const CodeNameMap instance;
351 35 return instance;
352 }
353 } // anonymous namespace
354
355 229 std::optional<InputCode> parse_input_name(std::string_view name)
356 {
357 // A source-tagged hex token ("Mouse:0xFE", "Gamepad:0x800") is the inverse of format_input_code's off-table
358 // form: it carries the device source so a non-keyboard code survives a config round-trip. No table name
359 // contains ':', so a token with one is only ever a tag; a malformed tag falls through to the table search
360 // (which cannot match it) and yields nullopt.
361
2/2
✓ Branch 3 → 4 taken 10 times.
✓ Branch 3 → 10 taken 219 times.
229 if (const size_t colon = name.find(':'); colon != std::string_view::npos)
362 {
363
2/2
✓ Branch 8 → 9 taken 7 times.
✓ Branch 8 → 10 taken 3 times.
10 if (const auto tagged = parse_source_tagged_hex(name.substr(0, colon), name.substr(colon + 1)))
364 {
365 7 return tagged;
366 }
367 }
368
369 222 const auto &idx = get_sorted_index();
370 222 size_t lo = 0;
371 222 size_t hi = idx.count;
372
2/2
✓ Branch 20 → 12 taken 1492 times.
✓ Branch 20 → 21 taken 56 times.
1548 while (lo < hi)
373 {
374 1492 const size_t mid = lo + (hi - lo) / 2;
375 1492 const int cmp = icompare(NAME_TABLE[idx.indices[mid]].name, name);
376
2/2
✓ Branch 14 → 15 taken 449 times.
✓ Branch 14 → 16 taken 1043 times.
1492 if (cmp < 0)
377 {
378 449 lo = mid + 1;
379 }
380
2/2
✓ Branch 16 → 17 taken 877 times.
✓ Branch 16 → 18 taken 166 times.
1043 else if (cmp > 0)
381 {
382 877 hi = mid;
383 }
384 else
385 {
386 166 return NAME_TABLE[idx.indices[mid]].code;
387 }
388 }
389 56 return std::nullopt;
390 }
391
392 35 std::string_view input_code_to_name(const InputCode &code)
393 {
394
1/2
✓ Branch 2 → 3 taken 35 times.
✗ Branch 2 → 12 not taken.
35 const auto &map = get_code_name_map().map;
395
1/2
✓ Branch 3 → 4 taken 35 times.
✗ Branch 3 → 12 not taken.
35 const auto it = map.find(code);
396
2/2
✓ Branch 6 → 7 taken 27 times.
✓ Branch 6 → 9 taken 8 times.
35 if (it != map.end())
397 {
398 27 return it->second;
399 }
400 8 return {};
401 }
402
403 25 std::string format_input_code(const InputCode &code)
404 {
405
1/2
✓ Branch 2 → 3 taken 25 times.
✗ Branch 2 → 44 not taken.
25 const auto name = input_code_to_name(code);
406
2/2
✓ Branch 4 → 5 taken 18 times.
✓ Branch 4 → 10 taken 7 times.
25 if (!name.empty())
407 {
408
1/2
✓ Branch 7 → 8 taken 18 times.
✗ Branch 7 → 28 not taken.
36 return std::string(name);
409 }
410 // Off-table fallback. A Keyboard code emits bare hex ("0xNN"), which round-trips through the config
411 // keyboard hex fallback and preserves the existing serialized form. Any other source is tagged with its
412 // device name ("Mouse:0xNN") so the source is not lost: an untagged hex always parses back as Keyboard,
413 // which would silently turn an off-table Mouse/Gamepad code into a keyboard key on a config round-trip.
414
2/2
✓ Branch 10 → 11 taken 3 times.
✓ Branch 10 → 13 taken 4 times.
7 if (code.source == InputSource::Keyboard)
415 {
416
1/2
✓ Branch 11 → 12 taken 3 times.
✗ Branch 11 → 44 not taken.
3 return format::format_hex(code.code, 2);
417 }
418
4/8
✓ Branch 13 → 14 taken 4 times.
✗ Branch 13 → 43 not taken.
✓ Branch 17 → 18 taken 4 times.
✗ Branch 17 → 35 not taken.
✓ Branch 18 → 19 taken 4 times.
✗ Branch 18 → 33 not taken.
✓ Branch 19 → 20 taken 4 times.
✗ Branch 19 → 31 not taken.
8 return std::string(input_source_to_string(code.source)) + ':' + format::format_hex(code.code, 2);
419 }
420
421 } // namespace DetourModKit
422