src/config.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * @file config.cpp | ||
| 3 | * @brief Implementation of configuration loading and management. | ||
| 4 | * | ||
| 5 | * Provides a system for registering configuration variables, loading their | ||
| 6 | * values from an INI file, and logging them. This allows mods to define their | ||
| 7 | * configuration needs and have DetourModKit handle the INI parsing and value | ||
| 8 | * assignment. | ||
| 9 | */ | ||
| 10 | |||
| 11 | #include "DetourModKit/config.hpp" | ||
| 12 | #include "DetourModKit/input_codes.hpp" | ||
| 13 | #include "DetourModKit/logger.hpp" | ||
| 14 | #include "DetourModKit/filesystem.hpp" | ||
| 15 | #include "DetourModKit/format.hpp" | ||
| 16 | #include "SimpleIni.h" | ||
| 17 | |||
| 18 | #include <windows.h> | ||
| 19 | #include <cerrno> | ||
| 20 | #include <cstdlib> | ||
| 21 | #include <filesystem> | ||
| 22 | #include <limits> | ||
| 23 | #include <memory> | ||
| 24 | #include <mutex> | ||
| 25 | #include <string> | ||
| 26 | #include <string_view> | ||
| 27 | #include <unordered_set> | ||
| 28 | #include <vector> | ||
| 29 | |||
| 30 | using namespace DetourModKit; | ||
| 31 | using namespace DetourModKit::Filesystem; | ||
| 32 | using namespace DetourModKit::String; | ||
| 33 | |||
| 34 | // Anonymous namespace for internal helpers and storage | ||
| 35 | namespace | ||
| 36 | { | ||
| 37 | /** | ||
| 38 | * @brief Parses a comma-separated string of input tokens into a vector of InputCodes. | ||
| 39 | * @details Each token is first matched against the named key table (case-insensitive). | ||
| 40 | * If no name matches, the token is parsed as a hexadecimal VK code (with or | ||
| 41 | * without 0x prefix), defaulting to InputSource::Keyboard. Handles inline | ||
| 42 | * semicolon comments, whitespace, and gracefully skips invalid tokens. | ||
| 43 | * @param input The raw string to parse. | ||
| 44 | * @return std::vector<InputCode> Parsed valid input codes. | ||
| 45 | */ | ||
| 46 | 90 | std::vector<InputCode> parse_input_code_list(const std::string &input) | |
| 47 | { | ||
| 48 | 90 | std::vector<InputCode> result; | |
| 49 | |||
| 50 | // Strip trailing comment from the full line | ||
| 51 | 90 | const size_t comment_pos = input.find(';'); | |
| 52 | const std::string effective = trim( | ||
| 53 |
3/8✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 90 times.
✗ Branch 4 → 6 not taken.
✗ Branch 4 → 82 not taken.
✓ Branch 5 → 6 taken 90 times.
✗ Branch 5 → 82 not taken.
✓ Branch 7 → 8 taken 90 times.
✗ Branch 7 → 80 not taken.
|
90 | (comment_pos != std::string::npos) ? input.substr(0, comment_pos) : input); |
| 54 |
1/2✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 90 times.
|
90 | if (effective.empty()) |
| 55 | { | ||
| 56 | ✗ | return result; | |
| 57 | } | ||
| 58 | |||
| 59 | // Walk comma-delimited tokens without istringstream overhead | ||
| 60 | 90 | size_t pos = 0; | |
| 61 |
2/2✓ Branch 75 → 13 taken 90 times.
✓ Branch 75 → 76 taken 90 times.
|
180 | while (pos < effective.size()) |
| 62 | { | ||
| 63 | 90 | const size_t comma = effective.find(',', pos); | |
| 64 |
1/2✓ Branch 14 → 15 taken 90 times.
✗ Branch 14 → 16 not taken.
|
90 | const size_t end = (comma != std::string::npos) ? comma : effective.size(); |
| 65 |
2/4✓ Branch 17 → 18 taken 90 times.
✗ Branch 17 → 85 not taken.
✓ Branch 19 → 20 taken 90 times.
✗ Branch 19 → 83 not taken.
|
90 | const std::string token = trim(effective.substr(pos, end - pos)); |
| 66 | 90 | pos = end + 1; | |
| 67 | |||
| 68 |
1/2✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 90 times.
|
90 | if (token.empty()) |
| 69 | { | ||
| 70 | ✗ | continue; | |
| 71 | } | ||
| 72 | |||
| 73 | // Try named key lookup first (case-insensitive) | ||
| 74 |
1/2✓ Branch 25 → 26 taken 90 times.
✗ Branch 25 → 87 not taken.
|
90 | auto named = parse_input_name(token); |
| 75 |
2/2✓ Branch 27 → 28 taken 54 times.
✓ Branch 27 → 31 taken 36 times.
|
90 | if (named.has_value()) |
| 76 | { | ||
| 77 |
1/2✓ Branch 29 → 30 taken 54 times.
✗ Branch 29 → 87 not taken.
|
54 | result.push_back(*named); |
| 78 | 54 | continue; | |
| 79 | } | ||
| 80 | |||
| 81 | // Fall back to hex parsing (defaults to Keyboard source) | ||
| 82 | 36 | size_t hex_start = 0; | |
| 83 |
8/10✓ Branch 32 → 33 taken 36 times.
✗ Branch 32 → 40 not taken.
✓ Branch 34 → 35 taken 33 times.
✓ Branch 34 → 40 taken 3 times.
✓ Branch 36 → 37 taken 1 time.
✓ Branch 36 → 39 taken 32 times.
✓ Branch 38 → 39 taken 1 time.
✗ Branch 38 → 40 not taken.
✓ Branch 41 → 42 taken 33 times.
✓ Branch 41 → 43 taken 3 times.
|
36 | if (token.size() >= 2 && token[0] == '0' && (token[1] == 'x' || token[1] == 'X')) |
| 84 | { | ||
| 85 | 33 | hex_start = 2; | |
| 86 | } | ||
| 87 |
2/2✓ Branch 44 → 45 taken 2 times.
✓ Branch 44 → 46 taken 34 times.
|
36 | if (hex_start >= token.size()) |
| 88 | { | ||
| 89 | 2 | continue; | |
| 90 | } | ||
| 91 | |||
| 92 | // Validate all remaining characters are hex digits | ||
| 93 | 34 | const std::string_view hex_part(token.data() + hex_start, token.size() - hex_start); | |
| 94 |
2/2✓ Branch 50 → 51 taken 3 times.
✓ Branch 50 → 52 taken 31 times.
|
34 | if (hex_part.find_first_not_of("0123456789abcdefABCDEF") != std::string_view::npos) |
| 95 | { | ||
| 96 | 3 | continue; | |
| 97 | } | ||
| 98 | |||
| 99 | // Convert via strtoul — no exception overhead on invalid input | ||
| 100 |
1/2✓ Branch 52 → 53 taken 31 times.
✗ Branch 52 → 87 not taken.
|
31 | errno = 0; |
| 101 | 31 | char *end_ptr = nullptr; | |
| 102 | 31 | const unsigned long value = std::strtoul(token.c_str() + hex_start, &end_ptr, 16); | |
| 103 |
6/8✓ Branch 56 → 57 taken 31 times.
✗ Branch 56 → 59 not taken.
✓ Branch 57 → 58 taken 31 times.
✗ Branch 57 → 87 not taken.
✓ Branch 58 → 59 taken 2 times.
✓ Branch 58 → 60 taken 29 times.
✓ Branch 61 → 62 taken 2 times.
✓ Branch 61 → 63 taken 29 times.
|
31 | if (end_ptr == token.c_str() + hex_start || errno == ERANGE) |
| 104 | { | ||
| 105 | 2 | continue; | |
| 106 | } | ||
| 107 |
2/2✓ Branch 64 → 65 taken 2 times.
✓ Branch 64 → 66 taken 27 times.
|
29 | if (value > static_cast<unsigned long>(std::numeric_limits<int>::max())) |
| 108 | { | ||
| 109 | 2 | continue; | |
| 110 | } | ||
| 111 | |||
| 112 |
1/2✓ Branch 66 → 67 taken 27 times.
✗ Branch 66 → 86 not taken.
|
27 | result.push_back(InputCode{InputSource::Keyboard, static_cast<int>(value)}); |
| 113 |
2/2✓ Branch 69 → 70 taken 27 times.
✓ Branch 69 → 72 taken 63 times.
|
90 | } |
| 114 | |||
| 115 | 90 | return result; | |
| 116 | 90 | } | |
| 117 | |||
| 118 | /** | ||
| 119 | * @brief Parses a single key combo string into a KeyCombo struct. | ||
| 120 | * @details Format: "modifier1+modifier2+trigger_key" where each token is a | ||
| 121 | * named key or hex VK code. The last '+'-delimited token is the trigger | ||
| 122 | * key, all preceding tokens are modifier keys (AND logic). This function | ||
| 123 | * expects a single combo with no commas; use parse_key_combo_list to | ||
| 124 | * split comma-separated alternatives first. | ||
| 125 | * @param input The raw string to parse (no commas expected). | ||
| 126 | * @return Config::KeyCombo Parsed key combination. | ||
| 127 | */ | ||
| 128 | 68 | Config::KeyCombo parse_key_combo(const std::string &input) | |
| 129 | { | ||
| 130 | 68 | Config::KeyCombo result; | |
| 131 | |||
| 132 |
1/2✓ Branch 3 → 4 taken 68 times.
✗ Branch 3 → 65 not taken.
|
68 | const std::string effective = trim(input); |
| 133 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 68 times.
|
68 | if (effective.empty()) |
| 134 | { | ||
| 135 | ✗ | return result; | |
| 136 | } | ||
| 137 | |||
| 138 | // Split by '+' to get segments | ||
| 139 | 68 | std::vector<std::string> segments; | |
| 140 | 68 | size_t pos = 0; | |
| 141 |
2/2✓ Branch 22 → 8 taken 90 times.
✓ Branch 22 → 23 taken 68 times.
|
158 | while (pos < effective.size()) |
| 142 | { | ||
| 143 | 90 | const size_t plus = effective.find('+', pos); | |
| 144 |
2/2✓ Branch 9 → 10 taken 68 times.
✓ Branch 9 → 11 taken 22 times.
|
90 | const size_t end = (plus != std::string::npos) ? plus : effective.size(); |
| 145 |
2/4✓ Branch 12 → 13 taken 90 times.
✗ Branch 12 → 51 not taken.
✓ Branch 14 → 15 taken 90 times.
✗ Branch 14 → 49 not taken.
|
90 | const std::string segment = trim(effective.substr(pos, end - pos)); |
| 146 | 90 | pos = end + 1; | |
| 147 |
1/2✓ Branch 17 → 18 taken 90 times.
✗ Branch 17 → 19 not taken.
|
90 | if (!segment.empty()) |
| 148 | { | ||
| 149 |
1/2✓ Branch 18 → 19 taken 90 times.
✗ Branch 18 → 52 not taken.
|
90 | segments.push_back(segment); |
| 150 | } | ||
| 151 | 90 | } | |
| 152 | |||
| 153 |
1/2✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 68 times.
|
68 | if (segments.empty()) |
| 154 | { | ||
| 155 | ✗ | return result; | |
| 156 | } | ||
| 157 | |||
| 158 | // Last segment is the trigger key | ||
| 159 |
1/2✓ Branch 27 → 28 taken 68 times.
✗ Branch 27 → 55 not taken.
|
68 | result.keys = parse_input_code_list(segments.back()); |
| 160 | |||
| 161 | // All preceding segments are individual modifier keys | ||
| 162 |
2/2✓ Branch 43 → 31 taken 22 times.
✓ Branch 43 → 44 taken 68 times.
|
90 | for (size_t i = 0; i + 1 < segments.size(); ++i) |
| 163 | { | ||
| 164 |
1/2✓ Branch 32 → 33 taken 22 times.
✗ Branch 32 → 60 not taken.
|
22 | auto mod_codes = parse_input_code_list(segments[i]); |
| 165 |
1/2✓ Branch 39 → 40 taken 22 times.
✗ Branch 39 → 56 not taken.
|
44 | result.modifiers.insert(result.modifiers.end(), mod_codes.begin(), mod_codes.end()); |
| 166 | 22 | } | |
| 167 | |||
| 168 | 68 | return result; | |
| 169 | 68 | } | |
| 170 | |||
| 171 | /** | ||
| 172 | * @brief Parses a comma-separated string of key combos into a KeyComboList. | ||
| 173 | * @details Commas at the top level separate independent combos (OR logic between | ||
| 174 | * combos). Each combo is parsed by parse_key_combo. Handles inline | ||
| 175 | * semicolon comments, whitespace, and gracefully skips empty/invalid combos. | ||
| 176 | * @param input The raw string to parse. | ||
| 177 | * @return Config::KeyComboList Parsed list of key combinations. | ||
| 178 | */ | ||
| 179 | 59 | Config::KeyComboList parse_key_combo_list(const std::string &input) | |
| 180 | { | ||
| 181 | 59 | Config::KeyComboList result; | |
| 182 | |||
| 183 | // Strip trailing comment from the full line | ||
| 184 | 59 | const size_t comment_pos = input.find(';'); | |
| 185 | const std::string effective = trim( | ||
| 186 |
5/8✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 56 times.
✓ Branch 4 → 6 taken 3 times.
✗ Branch 4 → 46 not taken.
✓ Branch 5 → 6 taken 56 times.
✗ Branch 5 → 46 not taken.
✓ Branch 7 → 8 taken 59 times.
✗ Branch 7 → 44 not taken.
|
59 | (comment_pos != std::string::npos) ? input.substr(0, comment_pos) : input); |
| 187 |
2/2✓ Branch 10 → 11 taken 19 times.
✓ Branch 10 → 12 taken 40 times.
|
59 | if (effective.empty()) |
| 188 | { | ||
| 189 | 19 | return result; | |
| 190 | } | ||
| 191 | |||
| 192 | // Split by comma into independent combo strings | ||
| 193 | 40 | size_t pos = 0; | |
| 194 |
2/2✓ Branch 39 → 13 taken 70 times.
✓ Branch 39 → 40 taken 40 times.
|
110 | while (pos < effective.size()) |
| 195 | { | ||
| 196 | 70 | const size_t comma = effective.find(',', pos); | |
| 197 |
2/2✓ Branch 14 → 15 taken 40 times.
✓ Branch 14 → 16 taken 30 times.
|
70 | const size_t end = (comma != std::string::npos) ? comma : effective.size(); |
| 198 |
2/4✓ Branch 17 → 18 taken 70 times.
✗ Branch 17 → 49 not taken.
✓ Branch 19 → 20 taken 70 times.
✗ Branch 19 → 47 not taken.
|
70 | const std::string combo_str = trim(effective.substr(pos, end - pos)); |
| 199 | 70 | pos = end + 1; | |
| 200 | |||
| 201 |
2/2✓ Branch 22 → 23 taken 2 times.
✓ Branch 22 → 24 taken 68 times.
|
70 | if (combo_str.empty()) |
| 202 | { | ||
| 203 | 2 | continue; | |
| 204 | } | ||
| 205 | |||
| 206 |
1/2✓ Branch 24 → 25 taken 68 times.
✗ Branch 24 → 52 not taken.
|
68 | auto combo = parse_key_combo(combo_str); |
| 207 |
2/2✓ Branch 26 → 27 taken 60 times.
✓ Branch 26 → 30 taken 8 times.
|
68 | if (!combo.keys.empty()) |
| 208 | { | ||
| 209 |
1/2✓ Branch 29 → 30 taken 60 times.
✗ Branch 29 → 50 not taken.
|
60 | result.push_back(std::move(combo)); |
| 210 | } | ||
| 211 |
2/2✓ Branch 33 → 34 taken 68 times.
✓ Branch 33 → 36 taken 2 times.
|
70 | } |
| 212 | |||
| 213 | 40 | return result; | |
| 214 | 59 | } | |
| 215 | |||
| 216 | /** | ||
| 217 | * @brief Formats a single KeyCombo as a human-readable string. | ||
| 218 | * @details Uses named keys where available, falls back to hex for unknown codes. | ||
| 219 | * @param combo The key combination to format. | ||
| 220 | * @return std::string Formatted string (e.g., "Ctrl+Shift+F3"). | ||
| 221 | */ | ||
| 222 | 2 | std::string format_key_combo(const Config::KeyCombo &combo) | |
| 223 | { | ||
| 224 | 2 | std::string result; | |
| 225 |
2/2✓ Branch 21 → 5 taken 1 time.
✓ Branch 21 → 22 taken 2 times.
|
5 | for (const auto &mod : combo.modifiers) |
| 226 | { | ||
| 227 |
3/6✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 38 not taken.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 36 not taken.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 34 not taken.
|
1 | result += DetourModKit::format_input_code(mod) + "+"; |
| 228 | } | ||
| 229 |
2/2✓ Branch 31 → 23 taken 2 times.
✓ Branch 31 → 32 taken 2 times.
|
4 | for (size_t i = 0; i < combo.keys.size(); ++i) |
| 230 | { | ||
| 231 |
1/2✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 2 times.
|
2 | if (i > 0) |
| 232 | { | ||
| 233 | ✗ | result += ","; | |
| 234 | } | ||
| 235 |
2/4✓ Branch 26 → 27 taken 2 times.
✗ Branch 26 → 43 not taken.
✓ Branch 27 → 28 taken 2 times.
✗ Branch 27 → 41 not taken.
|
2 | result += DetourModKit::format_input_code(combo.keys[i]); |
| 236 | } | ||
| 237 | 2 | return result; | |
| 238 | ✗ | } | |
| 239 | |||
| 240 | /** | ||
| 241 | * @brief Formats a KeyComboList as a human-readable string. | ||
| 242 | * @details Joins individual combos with commas. | ||
| 243 | * @param combos The list of key combinations to format. | ||
| 244 | * @return std::string Formatted string (e.g., "F3,Gamepad_LT+Gamepad_B"). | ||
| 245 | */ | ||
| 246 | 3 | std::string format_key_combo_list(const Config::KeyComboList &combos) | |
| 247 | { | ||
| 248 | 3 | std::string result; | |
| 249 |
2/2✓ Branch 12 → 4 taken 2 times.
✓ Branch 12 → 13 taken 3 times.
|
5 | for (size_t i = 0; i < combos.size(); ++i) |
| 250 | { | ||
| 251 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 2 times.
|
2 | if (i > 0) |
| 252 | { | ||
| 253 | ✗ | result += ","; | |
| 254 | } | ||
| 255 |
2/4✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 17 not taken.
✓ Branch 8 → 9 taken 2 times.
✗ Branch 8 → 15 not taken.
|
2 | result += format_key_combo(combos[i]); |
| 256 | } | ||
| 257 | 3 | return result; | |
| 258 | ✗ | } | |
| 259 | |||
| 260 | /** | ||
| 261 | * @brief Base class for typed configuration items. | ||
| 262 | * @details This allows storing different types of configuration items | ||
| 263 | * polymorphically in a collection. | ||
| 264 | */ | ||
| 265 | struct ConfigItemBase | ||
| 266 | { | ||
| 267 | std::string section; | ||
| 268 | std::string ini_key; | ||
| 269 | std::string log_key_name; | ||
| 270 | |||
| 271 | 94 | ConfigItemBase(std::string sec, std::string key, std::string log_name) | |
| 272 | 376 | : section(std::move(sec)), ini_key(std::move(key)), log_key_name(std::move(log_name)) {} | |
| 273 | 94 | virtual ~ConfigItemBase() = default; | |
| 274 | ConfigItemBase(const ConfigItemBase &) = delete; | ||
| 275 | ConfigItemBase &operator=(const ConfigItemBase &) = delete; | ||
| 276 | ConfigItemBase(ConfigItemBase &&) = delete; | ||
| 277 | ConfigItemBase &operator=(ConfigItemBase &&) = delete; | ||
| 278 | |||
| 279 | /** | ||
| 280 | * @brief Loads the configuration value from the INI file. | ||
| 281 | * @param ini Reference to the CSimpleIniA object. | ||
| 282 | * @param logger Reference to the Logger object. | ||
| 283 | */ | ||
| 284 | virtual void load(CSimpleIniA &ini, Logger &logger) = 0; | ||
| 285 | |||
| 286 | /** | ||
| 287 | * @brief Returns a deferred callback to invoke the setter outside the config mutex. | ||
| 288 | * @return A self-contained callable, or empty if no setter is configured. | ||
| 289 | */ | ||
| 290 | [[nodiscard]] virtual std::function<void()> take_deferred_apply() const = 0; | ||
| 291 | |||
| 292 | /** | ||
| 293 | * @brief Logs the current value of the configuration item. | ||
| 294 | * @param logger Reference to the Logger object. | ||
| 295 | */ | ||
| 296 | virtual void log_current_value(Logger &logger) const = 0; | ||
| 297 | }; | ||
| 298 | |||
| 299 | /** | ||
| 300 | * @brief Configuration item using std::function callback for value setting. | ||
| 301 | * @tparam T The data type of the configuration item (e.g., int, bool, std::string). | ||
| 302 | * @note Setter callbacks are invoked outside the config mutex to prevent deadlocks. | ||
| 303 | * See register_* and load() for the deferred invocation pattern. | ||
| 304 | */ | ||
| 305 | template <typename T> | ||
| 306 | struct CallbackConfigItem : public ConfigItemBase | ||
| 307 | { | ||
| 308 | std::function<void(T)> setter; // Callback function to set the value | ||
| 309 | T default_value; | ||
| 310 | T current_value; | ||
| 311 | |||
| 312 | 94 | CallbackConfigItem(std::string sec, std::string key, std::string log_name, | |
| 313 | std::function<void(T)> set_fn, T def_val) | ||
| 314 | 282 | : ConfigItemBase(std::move(sec), std::move(key), std::move(log_name)), | |
| 315 | 94 | setter(std::move(set_fn)), | |
| 316 |
2/4(anonymous namespace)::CallbackConfigItem<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::CallbackConfigItem(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::function<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >):
✓ Branch 18 → 19 taken 9 times.
✗ Branch 18 → 23 not taken.
(anonymous namespace)::CallbackConfigItem<std::vector<DetourModKit::Config::KeyCombo, std::allocator<DetourModKit::Config::KeyCombo> > >::CallbackConfigItem(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::function<void (std::vector<DetourModKit::Config::KeyCombo, std::allocator<DetourModKit::Config::KeyCombo> >)>, std::vector<DetourModKit::Config::KeyCombo, std::allocator<DetourModKit::Config::KeyCombo> >):
✓ Branch 18 → 19 taken 41 times.
✗ Branch 18 → 23 not taken.
|
94 | default_value(def_val), |
| 317 | 476 | current_value(std::move(def_val)) | |
| 318 | { | ||
| 319 | 94 | } | |
| 320 | |||
| 321 | void load(CSimpleIniA &ini, [[maybe_unused]] Logger &logger) override; | ||
| 322 | void log_current_value(Logger &logger) const override; | ||
| 323 | |||
| 324 | /// Returns a self-contained callback that invokes setter with current_value. | ||
| 325 | 86 | [[nodiscard]] std::function<void()> take_deferred_apply() const override | |
| 326 | { | ||
| 327 |
5/10(anonymous namespace)::CallbackConfigItem<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::take_deferred_apply() const:
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 7 times.
(anonymous namespace)::CallbackConfigItem<std::vector<DetourModKit::Config::KeyCombo, std::allocator<DetourModKit::Config::KeyCombo> > >::take_deferred_apply() const:
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 34 times.
(anonymous namespace)::CallbackConfigItem<bool>::take_deferred_apply() const:
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 9 times.
(anonymous namespace)::CallbackConfigItem<float>::take_deferred_apply() const:
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 8 times.
(anonymous namespace)::CallbackConfigItem<int>::take_deferred_apply() const:
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 28 times.
|
86 | if (!setter) |
| 328 | ✗ | return {}; | |
| 329 |
12/34(anonymous namespace)::CallbackConfigItem<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::take_deferred_apply() const:
✓ Branch 5 → 6 taken 7 times.
✗ Branch 5 → 19 not taken.
✓ Branch 6 → 7 taken 7 times.
✗ Branch 6 → 16 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 7 times.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
(anonymous namespace)::CallbackConfigItem<std::vector<DetourModKit::Config::KeyCombo, std::allocator<DetourModKit::Config::KeyCombo> > >::take_deferred_apply() const:
✓ Branch 5 → 6 taken 34 times.
✗ Branch 5 → 19 not taken.
✓ Branch 6 → 7 taken 34 times.
✗ Branch 6 → 16 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 34 times.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
(anonymous namespace)::CallbackConfigItem<bool>::take_deferred_apply() const:
✓ Branch 5 → 6 taken 9 times.
✗ Branch 5 → 18 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 9 times.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
(anonymous namespace)::CallbackConfigItem<float>::take_deferred_apply() const:
✓ Branch 5 → 6 taken 8 times.
✗ Branch 5 → 18 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 8 times.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
(anonymous namespace)::CallbackConfigItem<int>::take_deferred_apply() const:
✓ Branch 5 → 6 taken 28 times.
✗ Branch 5 → 18 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 28 times.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
|
344 | return [fn = setter, val = current_value]() mutable |
| 330 |
6/12None:
✓ Branch 5 → 6 taken 41 times.
✗ Branch 5 → 8 not taken.
(anonymous namespace)::CallbackConfigItem<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::take_deferred_apply() const:
✓ Branch 7 → 8 taken 7 times.
✗ Branch 7 → 14 not taken.
(anonymous namespace)::CallbackConfigItem<std::vector<DetourModKit::Config::KeyCombo, std::allocator<DetourModKit::Config::KeyCombo> > >::take_deferred_apply() const:
✓ Branch 7 → 8 taken 34 times.
✗ Branch 7 → 14 not taken.
(anonymous namespace)::CallbackConfigItem<bool>::take_deferred_apply() const:
✓ Branch 6 → 7 taken 9 times.
✗ Branch 6 → 13 not taken.
(anonymous namespace)::CallbackConfigItem<float>::take_deferred_apply() const:
✓ Branch 6 → 7 taken 8 times.
✗ Branch 6 → 13 not taken.
(anonymous namespace)::CallbackConfigItem<int>::take_deferred_apply() const:
✓ Branch 6 → 7 taken 28 times.
✗ Branch 6 → 13 not taken.
|
258 | { fn(std::move(val)); }; |
| 331 | } | ||
| 332 | }; | ||
| 333 | |||
| 334 | // --- Specializations for CallbackConfigItem<T>::load and ::log_current_value --- | ||
| 335 | |||
| 336 | // For int | ||
| 337 | template <> | ||
| 338 | 28 | void CallbackConfigItem<int>::load(CSimpleIniA &ini, [[maybe_unused]] Logger &logger) | |
| 339 | { | ||
| 340 | 28 | current_value = static_cast<int>(ini.GetLongValue(section.c_str(), ini_key.c_str(), default_value)); | |
| 341 | 28 | } | |
| 342 | |||
| 343 | template <> | ||
| 344 | 4 | void CallbackConfigItem<int>::log_current_value(Logger &logger) const | |
| 345 | { | ||
| 346 |
1/2✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 4 not taken.
|
4 | logger.debug("Config: {} = {}", ini_key, current_value); |
| 347 | 4 | } | |
| 348 | |||
| 349 | // For float | ||
| 350 | template <> | ||
| 351 | 8 | void CallbackConfigItem<float>::load(CSimpleIniA &ini, [[maybe_unused]] Logger &logger) | |
| 352 | { | ||
| 353 | 8 | current_value = static_cast<float>(ini.GetDoubleValue(section.c_str(), ini_key.c_str(), static_cast<double>(default_value))); | |
| 354 | 8 | } | |
| 355 | |||
| 356 | template <> | ||
| 357 | 1 | void CallbackConfigItem<float>::log_current_value(Logger &logger) const | |
| 358 | { | ||
| 359 |
1/2✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 4 not taken.
|
1 | logger.debug("Config: {} = {}", ini_key, current_value); |
| 360 | 1 | } | |
| 361 | |||
| 362 | // For bool | ||
| 363 | template <> | ||
| 364 | 9 | void CallbackConfigItem<bool>::load(CSimpleIniA &ini, [[maybe_unused]] Logger &logger) | |
| 365 | { | ||
| 366 | 9 | current_value = ini.GetBoolValue(section.c_str(), ini_key.c_str(), default_value); | |
| 367 | 9 | } | |
| 368 | |||
| 369 | template <> | ||
| 370 | 1 | void CallbackConfigItem<bool>::log_current_value(Logger &logger) const | |
| 371 | { | ||
| 372 |
2/4✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 4 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 7 not taken.
|
1 | logger.debug("Config: {} = {}", ini_key, current_value ? "true" : "false"); |
| 373 | 1 | } | |
| 374 | |||
| 375 | // For std::string | ||
| 376 | template <> | ||
| 377 | 7 | void CallbackConfigItem<std::string>::load(CSimpleIniA &ini, [[maybe_unused]] Logger &logger) | |
| 378 | { | ||
| 379 | 7 | current_value = ini.GetValue(section.c_str(), ini_key.c_str(), default_value.c_str()); | |
| 380 | 7 | } | |
| 381 | |||
| 382 | template <> | ||
| 383 | 2 | void CallbackConfigItem<std::string>::log_current_value(Logger &logger) const | |
| 384 | { | ||
| 385 |
1/2✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 4 not taken.
|
2 | logger.debug("Config: {} = \"{}\"", ini_key, current_value); |
| 386 | 2 | } | |
| 387 | |||
| 388 | // For Config::KeyComboList (list of key combinations) | ||
| 389 | template <> | ||
| 390 | 34 | void CallbackConfigItem<Config::KeyComboList>::load(CSimpleIniA &ini, [[maybe_unused]] Logger &logger) | |
| 391 | { | ||
| 392 | 34 | const char *ini_value_str = ini.GetValue(section.c_str(), ini_key.c_str(), nullptr); | |
| 393 |
2/2✓ Branch 5 → 6 taken 18 times.
✓ Branch 5 → 15 taken 16 times.
|
34 | if (ini_value_str != nullptr) |
| 394 | { | ||
| 395 |
2/4✓ Branch 8 → 9 taken 18 times.
✗ Branch 8 → 19 not taken.
✓ Branch 9 → 10 taken 18 times.
✗ Branch 9 → 17 not taken.
|
36 | current_value = parse_key_combo_list(ini_value_str); |
| 396 | } | ||
| 397 | else | ||
| 398 | { | ||
| 399 | 16 | current_value = default_value; | |
| 400 | } | ||
| 401 | 34 | } | |
| 402 | |||
| 403 | template <> | ||
| 404 | 3 | void CallbackConfigItem<Config::KeyComboList>::log_current_value(Logger &logger) const | |
| 405 | { | ||
| 406 |
1/2✓ Branch 2 → 3 taken 3 times.
✗ Branch 2 → 15 not taken.
|
3 | const std::string formatted = format_key_combo_list(current_value); |
| 407 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 7 taken 2 times.
|
3 | if (formatted.empty()) |
| 408 | { | ||
| 409 |
1/2✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 11 not taken.
|
1 | logger.debug("Config: {} = (none)", ini_key); |
| 410 | } | ||
| 411 | else | ||
| 412 | { | ||
| 413 |
1/2✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 12 not taken.
|
2 | logger.debug("Config: {} = {}", ini_key, formatted); |
| 414 | } | ||
| 415 | 3 | } | |
| 416 | |||
| 417 | // --- Global storage for registered configuration items --- | ||
| 418 | 316 | std::mutex &getConfigMutex() | |
| 419 | { | ||
| 420 |
3/4✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 8 taken 315 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 8 not taken.
|
316 | static std::mutex mtx; |
| 421 | 316 | return mtx; | |
| 422 | } | ||
| 423 | |||
| 424 | 454 | std::vector<std::unique_ptr<ConfigItemBase>> &getRegisteredConfigItems() | |
| 425 | { | ||
| 426 | // Function-local static to ensure controlled initialization order. | ||
| 427 |
3/4✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 7 taken 453 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 7 not taken.
|
454 | static std::vector<std::unique_ptr<ConfigItemBase>> s_registered_items; |
| 428 | 454 | return s_registered_items; | |
| 429 | } | ||
| 430 | |||
| 431 | /// Replaces an existing item with the same section+key, or appends if none found. | ||
| 432 | /// Caller must hold getConfigMutex(). | ||
| 433 | 94 | void replace_or_append(std::unique_ptr<ConfigItemBase> item) | |
| 434 | { | ||
| 435 | 94 | auto &items = getRegisteredConfigItems(); | |
| 436 |
2/2✓ Branch 31 → 5 taken 41 times.
✓ Branch 31 → 32 taken 91 times.
|
226 | for (auto &existing : items) |
| 437 | { | ||
| 438 |
6/6✓ Branch 10 → 11 taken 32 times.
✓ Branch 10 → 16 taken 9 times.
✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 16 taken 29 times.
✓ Branch 17 → 18 taken 3 times.
✓ Branch 17 → 22 taken 38 times.
|
41 | if (existing->section == item->section && existing->ini_key == item->ini_key) |
| 439 | { | ||
| 440 | 3 | existing = std::move(item); | |
| 441 | 3 | return; | |
| 442 | } | ||
| 443 | } | ||
| 444 | 91 | items.push_back(std::move(item)); | |
| 445 | } | ||
| 446 | |||
| 447 | /** | ||
| 448 | * @brief Determines the full absolute path for the INI configuration file. | ||
| 449 | */ | ||
| 450 | 68 | std::string getIniFilePath(const std::string &ini_filename, Logger &logger) | |
| 451 | { | ||
| 452 |
1/2✓ Branch 2 → 3 taken 68 times.
✗ Branch 2 → 67 not taken.
|
68 | std::string module_dir = get_runtime_directory(); |
| 453 | |||
| 454 |
4/8✓ Branch 4 → 5 taken 68 times.
✗ Branch 4 → 7 not taken.
✓ Branch 5 → 6 taken 68 times.
✗ Branch 5 → 65 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 68 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 13 taken 68 times.
|
68 | if (module_dir.empty() || module_dir == ".") |
| 455 | { | ||
| 456 | ✗ | logger.warning("Config: Could not reliably determine module directory or it's current working directory. Using relative path for INI: {}", ini_filename); | |
| 457 | ✗ | return ini_filename; // Fallback to relative path | |
| 458 | } | ||
| 459 | |||
| 460 | try | ||
| 461 | { | ||
| 462 |
3/6✓ Branch 13 → 14 taken 68 times.
✗ Branch 13 → 37 not taken.
✓ Branch 14 → 15 taken 68 times.
✗ Branch 14 → 34 not taken.
✓ Branch 15 → 16 taken 68 times.
✗ Branch 15 → 32 not taken.
|
68 | std::filesystem::path ini_path_obj = std::filesystem::path(module_dir) / ini_filename; |
| 463 |
2/4✓ Branch 18 → 19 taken 68 times.
✗ Branch 18 → 40 not taken.
✓ Branch 19 → 20 taken 68 times.
✗ Branch 19 → 38 not taken.
|
68 | std::string full_path = ini_path_obj.lexically_normal().string(); // Normalize (e.g., C:/path/./file -> C:/path/file) |
| 464 |
1/2✓ Branch 21 → 22 taken 68 times.
✗ Branch 21 → 41 not taken.
|
68 | logger.debug("Config: Determined INI file path: {}", full_path); |
| 465 | 68 | return full_path; | |
| 466 | 68 | } | |
| 467 | ✗ | catch (const std::filesystem::filesystem_error &fs_err) | |
| 468 | { | ||
| 469 | ✗ | logger.warning("Config: Filesystem error constructing INI path: {}. Using relative path for INI: {}", fs_err.what(), ini_filename); | |
| 470 | ✗ | } | |
| 471 | ✗ | catch (const std::exception &e) // Catch other potential exceptions | |
| 472 | { | ||
| 473 | ✗ | logger.warning("Config: General error constructing INI path: {}. Using relative path for INI: {}", e.what(), ini_filename); | |
| 474 | ✗ | } | |
| 475 | ✗ | return ini_filename; // Fallback | |
| 476 | 68 | } | |
| 477 | |||
| 478 | } // anonymous namespace | ||
| 479 | |||
| 480 | 27 | void DetourModKit::Config::register_int(std::string_view section, std::string_view ini_key, | |
| 481 | std::string_view log_key_name, std::function<void(int)> setter, | ||
| 482 | int default_value) | ||
| 483 | { | ||
| 484 | 27 | std::function<void()> deferred; | |
| 485 | { | ||
| 486 |
1/2✓ Branch 4 → 5 taken 27 times.
✗ Branch 4 → 71 not taken.
|
27 | std::lock_guard<std::mutex> lock(getConfigMutex()); |
| 487 |
1/2✓ Branch 16 → 17 taken 27 times.
✗ Branch 16 → 39 not taken.
|
27 | replace_or_append( |
| 488 |
4/8✓ Branch 7 → 8 taken 27 times.
✗ Branch 7 → 57 not taken.
✓ Branch 10 → 11 taken 27 times.
✗ Branch 10 → 51 not taken.
✓ Branch 13 → 14 taken 27 times.
✗ Branch 13 → 45 not taken.
✓ Branch 14 → 15 taken 27 times.
✗ Branch 14 → 43 not taken.
|
162 | std::make_unique<CallbackConfigItem<int>>(std::string(section), std::string(ini_key), std::string(log_key_name), setter, default_value)); |
| 489 |
2/2✓ Branch 26 → 27 taken 24 times.
✓ Branch 26 → 33 taken 3 times.
|
27 | if (setter) |
| 490 | { | ||
| 491 |
3/8✓ Branch 27 → 28 taken 24 times.
✗ Branch 27 → 68 not taken.
✓ Branch 28 → 29 taken 24 times.
✗ Branch 28 → 63 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 24 times.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
|
48 | deferred = [setter, default_value]() { setter(default_value); }; |
| 492 | } | ||
| 493 | 27 | } | |
| 494 |
2/2✓ Branch 35 → 36 taken 24 times.
✓ Branch 35 → 37 taken 3 times.
|
27 | if (deferred) |
| 495 | { | ||
| 496 |
1/2✓ Branch 36 → 37 taken 24 times.
✗ Branch 36 → 72 not taken.
|
24 | deferred(); |
| 497 | } | ||
| 498 | 27 | } | |
| 499 | |||
| 500 | 8 | void DetourModKit::Config::register_float(std::string_view section, std::string_view ini_key, | |
| 501 | std::string_view log_key_name, std::function<void(float)> setter, | ||
| 502 | float default_value) | ||
| 503 | { | ||
| 504 | 8 | std::function<void()> deferred; | |
| 505 | { | ||
| 506 |
1/2✓ Branch 4 → 5 taken 8 times.
✗ Branch 4 → 71 not taken.
|
8 | std::lock_guard<std::mutex> lock(getConfigMutex()); |
| 507 |
1/2✓ Branch 16 → 17 taken 8 times.
✗ Branch 16 → 39 not taken.
|
8 | replace_or_append( |
| 508 |
4/8✓ Branch 7 → 8 taken 8 times.
✗ Branch 7 → 57 not taken.
✓ Branch 10 → 11 taken 8 times.
✗ Branch 10 → 51 not taken.
✓ Branch 13 → 14 taken 8 times.
✗ Branch 13 → 45 not taken.
✓ Branch 14 → 15 taken 8 times.
✗ Branch 14 → 43 not taken.
|
48 | std::make_unique<CallbackConfigItem<float>>(std::string(section), std::string(ini_key), std::string(log_key_name), setter, default_value)); |
| 509 |
1/2✓ Branch 26 → 27 taken 8 times.
✗ Branch 26 → 33 not taken.
|
8 | if (setter) |
| 510 | { | ||
| 511 |
3/8✓ Branch 27 → 28 taken 8 times.
✗ Branch 27 → 68 not taken.
✓ Branch 28 → 29 taken 8 times.
✗ Branch 28 → 63 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 8 times.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
|
16 | deferred = [setter, default_value]() { setter(default_value); }; |
| 512 | } | ||
| 513 | 8 | } | |
| 514 |
1/2✓ Branch 35 → 36 taken 8 times.
✗ Branch 35 → 37 not taken.
|
8 | if (deferred) |
| 515 | { | ||
| 516 |
1/2✓ Branch 36 → 37 taken 8 times.
✗ Branch 36 → 72 not taken.
|
8 | deferred(); |
| 517 | } | ||
| 518 | 8 | } | |
| 519 | |||
| 520 | 9 | void DetourModKit::Config::register_bool(std::string_view section, std::string_view ini_key, | |
| 521 | std::string_view log_key_name, std::function<void(bool)> setter, | ||
| 522 | bool default_value) | ||
| 523 | { | ||
| 524 | 9 | std::function<void()> deferred; | |
| 525 | { | ||
| 526 |
1/2✓ Branch 4 → 5 taken 9 times.
✗ Branch 4 → 71 not taken.
|
9 | std::lock_guard<std::mutex> lock(getConfigMutex()); |
| 527 |
1/2✓ Branch 16 → 17 taken 9 times.
✗ Branch 16 → 39 not taken.
|
9 | replace_or_append( |
| 528 |
4/8✓ Branch 7 → 8 taken 9 times.
✗ Branch 7 → 57 not taken.
✓ Branch 10 → 11 taken 9 times.
✗ Branch 10 → 51 not taken.
✓ Branch 13 → 14 taken 9 times.
✗ Branch 13 → 45 not taken.
✓ Branch 14 → 15 taken 9 times.
✗ Branch 14 → 43 not taken.
|
54 | std::make_unique<CallbackConfigItem<bool>>(std::string(section), std::string(ini_key), std::string(log_key_name), setter, default_value)); |
| 529 |
1/2✓ Branch 26 → 27 taken 9 times.
✗ Branch 26 → 33 not taken.
|
9 | if (setter) |
| 530 | { | ||
| 531 |
3/8✓ Branch 27 → 28 taken 9 times.
✗ Branch 27 → 68 not taken.
✓ Branch 28 → 29 taken 9 times.
✗ Branch 28 → 63 not taken.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 9 times.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
|
18 | deferred = [setter, default_value]() { setter(default_value); }; |
| 532 | } | ||
| 533 | 9 | } | |
| 534 |
1/2✓ Branch 35 → 36 taken 9 times.
✗ Branch 35 → 37 not taken.
|
9 | if (deferred) |
| 535 | { | ||
| 536 |
1/2✓ Branch 36 → 37 taken 9 times.
✗ Branch 36 → 72 not taken.
|
9 | deferred(); |
| 537 | } | ||
| 538 | 9 | } | |
| 539 | |||
| 540 | 9 | void DetourModKit::Config::register_string(std::string_view section, std::string_view ini_key, | |
| 541 | std::string_view log_key_name, std::function<void(const std::string &)> setter, | ||
| 542 | std::string default_value) | ||
| 543 | { | ||
| 544 | 9 | std::function<void()> deferred; | |
| 545 | { | ||
| 546 |
1/2✓ Branch 4 → 5 taken 9 times.
✗ Branch 4 → 74 not taken.
|
9 | std::lock_guard<std::mutex> lock(getConfigMutex()); |
| 547 |
1/2✓ Branch 16 → 17 taken 9 times.
✗ Branch 16 → 42 not taken.
|
9 | replace_or_append( |
| 548 |
4/8✓ Branch 7 → 8 taken 9 times.
✗ Branch 7 → 60 not taken.
✓ Branch 10 → 11 taken 9 times.
✗ Branch 10 → 54 not taken.
✓ Branch 13 → 14 taken 9 times.
✗ Branch 13 → 48 not taken.
✓ Branch 14 → 15 taken 9 times.
✗ Branch 14 → 46 not taken.
|
54 | std::make_unique<CallbackConfigItem<std::string>>(std::string(section), std::string(ini_key), std::string(log_key_name), setter, default_value)); |
| 549 |
2/2✓ Branch 26 → 27 taken 8 times.
✓ Branch 26 → 36 taken 1 time.
|
9 | if (setter) |
| 550 | { | ||
| 551 |
3/8✓ Branch 27 → 28 taken 8 times.
✗ Branch 27 → 71 not taken.
✓ Branch 31 → 32 taken 8 times.
✗ Branch 31 → 66 not taken.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 8 times.
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 70 not taken.
|
24 | deferred = [setter, val = std::move(default_value)]() { setter(val); }; |
| 552 | } | ||
| 553 | 9 | } | |
| 554 |
2/2✓ Branch 38 → 39 taken 8 times.
✓ Branch 38 → 40 taken 1 time.
|
9 | if (deferred) |
| 555 | { | ||
| 556 |
1/2✓ Branch 39 → 40 taken 8 times.
✗ Branch 39 → 75 not taken.
|
8 | deferred(); |
| 557 | } | ||
| 558 | 9 | } | |
| 559 | |||
| 560 | 41 | void DetourModKit::Config::register_key_combo(std::string_view section, std::string_view ini_key, | |
| 561 | std::string_view log_key_name, std::function<void(const KeyComboList &)> setter, | ||
| 562 | std::string_view default_value_str) | ||
| 563 | { | ||
| 564 |
2/4✓ Branch 4 → 5 taken 41 times.
✗ Branch 4 → 51 not taken.
✓ Branch 5 → 6 taken 41 times.
✗ Branch 5 → 49 not taken.
|
41 | Config::KeyComboList default_combos = parse_key_combo_list(std::string(default_value_str)); |
| 565 | |||
| 566 | 41 | std::function<void()> deferred; | |
| 567 | { | ||
| 568 |
1/2✓ Branch 10 → 11 taken 41 times.
✗ Branch 10 → 87 not taken.
|
41 | std::lock_guard<std::mutex> lock(getConfigMutex()); |
| 569 |
1/2✓ Branch 22 → 23 taken 41 times.
✗ Branch 22 → 55 not taken.
|
41 | replace_or_append( |
| 570 |
4/8✓ Branch 13 → 14 taken 41 times.
✗ Branch 13 → 73 not taken.
✓ Branch 16 → 17 taken 41 times.
✗ Branch 16 → 67 not taken.
✓ Branch 19 → 20 taken 41 times.
✗ Branch 19 → 61 not taken.
✓ Branch 20 → 21 taken 41 times.
✗ Branch 20 → 59 not taken.
|
246 | std::make_unique<CallbackConfigItem<Config::KeyComboList>>(std::string(section), std::string(ini_key), std::string(log_key_name), setter, default_combos)); |
| 571 |
2/2✓ Branch 32 → 33 taken 40 times.
✓ Branch 32 → 42 taken 1 time.
|
41 | if (setter) |
| 572 | { | ||
| 573 |
3/8✓ Branch 33 → 34 taken 40 times.
✗ Branch 33 → 84 not taken.
✓ Branch 37 → 38 taken 40 times.
✗ Branch 37 → 79 not taken.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 40 times.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 83 not taken.
|
120 | deferred = [setter, combos = std::move(default_combos)]() { setter(combos); }; |
| 574 | } | ||
| 575 | 41 | } | |
| 576 |
2/2✓ Branch 44 → 45 taken 40 times.
✓ Branch 44 → 46 taken 1 time.
|
41 | if (deferred) |
| 577 | { | ||
| 578 |
1/2✓ Branch 45 → 46 taken 40 times.
✗ Branch 45 → 88 not taken.
|
40 | deferred(); |
| 579 | } | ||
| 580 | 41 | } | |
| 581 | |||
| 582 | 68 | void DetourModKit::Config::load(std::string_view ini_filename) | |
| 583 | { | ||
| 584 | 68 | std::vector<std::function<void()>> deferred_callbacks; | |
| 585 | |||
| 586 | { | ||
| 587 |
1/2✓ Branch 3 → 4 taken 68 times.
✗ Branch 3 → 89 not taken.
|
68 | std::lock_guard<std::mutex> lock(getConfigMutex()); |
| 588 | |||
| 589 |
1/2✓ Branch 4 → 5 taken 68 times.
✗ Branch 4 → 87 not taken.
|
68 | Logger &logger = Logger::get_instance(); |
| 590 |
2/4✓ Branch 7 → 8 taken 68 times.
✗ Branch 7 → 71 not taken.
✓ Branch 8 → 9 taken 68 times.
✗ Branch 8 → 69 not taken.
|
68 | std::string ini_path = getIniFilePath(std::string(ini_filename), logger); |
| 591 | 68 | CSimpleIniA ini; | |
| 592 | 68 | ini.SetUnicode(false); // Assume ASCII/MBCS INI | |
| 593 | 68 | ini.SetMultiKey(false); // Disallow duplicate keys in a section | |
| 594 | |||
| 595 |
1/2✓ Branch 15 → 16 taken 68 times.
✗ Branch 15 → 83 not taken.
|
68 | SI_Error rc = ini.LoadFile(ini_path.c_str()); |
| 596 |
2/2✓ Branch 16 → 17 taken 26 times.
✓ Branch 16 → 19 taken 42 times.
|
68 | if (rc < 0) |
| 597 | { | ||
| 598 |
1/2✓ Branch 17 → 18 taken 26 times.
✗ Branch 17 → 75 not taken.
|
26 | logger.error("Config: Failed to open '{}' (error {}). Using defaults.", ini_path, rc); |
| 599 | } | ||
| 600 | else | ||
| 601 | { | ||
| 602 |
1/2✓ Branch 19 → 20 taken 42 times.
✗ Branch 19 → 76 not taken.
|
42 | logger.debug("Config: Opened {}", ini_path); |
| 603 | } | ||
| 604 | |||
| 605 | // Read all values under lock, but defer setter callbacks | ||
| 606 |
2/2✓ Branch 45 → 24 taken 86 times.
✓ Branch 45 → 46 taken 68 times.
|
222 | for (const auto &item : getRegisteredConfigItems()) |
| 607 | { | ||
| 608 |
1/2✓ Branch 27 → 28 taken 86 times.
✗ Branch 27 → 79 not taken.
|
86 | item->load(ini, logger); |
| 609 |
1/2✓ Branch 29 → 30 taken 86 times.
✗ Branch 29 → 79 not taken.
|
86 | auto cb = item->take_deferred_apply(); |
| 610 |
1/2✓ Branch 31 → 32 taken 86 times.
✗ Branch 31 → 35 not taken.
|
86 | if (cb) |
| 611 | { | ||
| 612 |
1/2✓ Branch 34 → 35 taken 86 times.
✗ Branch 34 → 77 not taken.
|
86 | deferred_callbacks.push_back(std::move(cb)); |
| 613 | } | ||
| 614 | 86 | } | |
| 615 | |||
| 616 |
1/2✓ Branch 48 → 49 taken 68 times.
✗ Branch 48 → 81 not taken.
|
68 | logger.info("Config: Loaded {} items from {}", getRegisteredConfigItems().size(), ini_path); |
| 617 | 68 | } | |
| 618 | |||
| 619 | // Invoke setter callbacks outside the config mutex to prevent deadlocks | ||
| 620 |
2/2✓ Branch 66 → 54 taken 86 times.
✓ Branch 66 → 67 taken 68 times.
|
222 | for (auto &cb : deferred_callbacks) |
| 621 | { | ||
| 622 |
1/2✓ Branch 56 → 57 taken 86 times.
✗ Branch 56 → 90 not taken.
|
86 | cb(); |
| 623 | } | ||
| 624 | 68 | } | |
| 625 | |||
| 626 | 8 | void DetourModKit::Config::log_all() | |
| 627 | { | ||
| 628 |
1/2✓ Branch 3 → 4 taken 8 times.
✗ Branch 3 → 56 not taken.
|
8 | std::lock_guard<std::mutex> lock(getConfigMutex()); |
| 629 | |||
| 630 |
1/2✓ Branch 4 → 5 taken 8 times.
✗ Branch 4 → 54 not taken.
|
8 | Logger &logger = Logger::get_instance(); |
| 631 | 8 | const auto &items = getRegisteredConfigItems(); | |
| 632 |
2/2✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 10 taken 6 times.
|
8 | if (items.empty()) |
| 633 | { | ||
| 634 |
1/2✓ Branch 8 → 9 taken 2 times.
✗ Branch 8 → 45 not taken.
|
2 | logger.info("Config: No configuration items registered."); |
| 635 | 2 | return; | |
| 636 | } | ||
| 637 | |||
| 638 | ✗ | logger.info("Config: {} registered values across {} section(s)", | |
| 639 |
1/2✓ Branch 12 → 13 taken 6 times.
✗ Branch 12 → 46 not taken.
|
6 | items.size(), [&items]() |
| 640 | { | ||
| 641 | 6 | std::unordered_set<std::string_view> seen; | |
| 642 |
2/2✓ Branch 19 → 5 taken 11 times.
✓ Branch 19 → 20 taken 6 times.
|
23 | for (const auto &item : items) |
| 643 | { | ||
| 644 |
1/2✓ Branch 9 → 10 taken 11 times.
✗ Branch 9 → 24 not taken.
|
11 | seen.insert(item->section); |
| 645 | } | ||
| 646 | 12 | return seen.size(); | |
| 647 |
1/2✓ Branch 10 → 11 taken 6 times.
✗ Branch 10 → 48 not taken.
|
12 | }()); |
| 648 | |||
| 649 | 6 | std::string current_section; | |
| 650 |
2/2✓ Branch 36 → 16 taken 11 times.
✓ Branch 36 → 37 taken 6 times.
|
23 | for (const auto &item : items) |
| 651 | { | ||
| 652 |
2/2✓ Branch 20 → 21 taken 7 times.
✓ Branch 20 → 25 taken 4 times.
|
11 | if (item->section != current_section) |
| 653 | { | ||
| 654 |
1/2✓ Branch 22 → 23 taken 7 times.
✗ Branch 22 → 51 not taken.
|
7 | current_section = item->section; |
| 655 |
1/2✓ Branch 23 → 24 taken 7 times.
✗ Branch 23 → 50 not taken.
|
7 | logger.debug("Config: [{}]", current_section); |
| 656 | } | ||
| 657 |
1/2✓ Branch 26 → 27 taken 11 times.
✗ Branch 26 → 51 not taken.
|
11 | item->log_current_value(logger); |
| 658 | } | ||
| 659 |
2/2✓ Branch 40 → 41 taken 6 times.
✓ Branch 40 → 43 taken 2 times.
|
8 | } |
| 660 | |||
| 661 | 146 | void DetourModKit::Config::clear_registered_items() | |
| 662 | { | ||
| 663 |
1/2✓ Branch 3 → 4 taken 146 times.
✗ Branch 3 → 20 not taken.
|
146 | std::lock_guard<std::mutex> lock(getConfigMutex()); |
| 664 | |||
| 665 |
1/2✓ Branch 4 → 5 taken 146 times.
✗ Branch 4 → 18 not taken.
|
146 | Logger &logger = Logger::get_instance(); |
| 666 | 146 | size_t count = getRegisteredConfigItems().size(); | |
| 667 |
2/2✓ Branch 7 → 8 taken 70 times.
✓ Branch 7 → 12 taken 76 times.
|
146 | if (count > 0) |
| 668 | { | ||
| 669 | 70 | getRegisteredConfigItems().clear(); | |
| 670 |
1/2✓ Branch 10 → 11 taken 70 times.
✗ Branch 10 → 16 not taken.
|
70 | logger.debug("Config: Cleared {} registered configuration items.", count); |
| 671 | } | ||
| 672 | else | ||
| 673 | { | ||
| 674 |
1/2✓ Branch 12 → 13 taken 76 times.
✗ Branch 12 → 17 not taken.
|
76 | logger.debug("Config: clear_registered_items called, but no items were registered."); |
| 675 | } | ||
| 676 | 146 | } | |
| 677 |