| 1 | // Copyright (c) 2017-2023, The Khronos Group Inc. |
| 2 | // Copyright (c) 2017-2019 Valve Corporation |
| 3 | // Copyright (c) 2017-2019 LunarG, Inc. |
| 4 | // |
| 5 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
| 6 | // |
| 7 | // Initial Authors: Mark Young <marky@lunarg.com>, Dave Houlton <daveh@lunarg.com> |
| 8 | // |
| 9 | |
| 10 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) |
| 11 | #define _CRT_SECURE_NO_WARNINGS |
| 12 | #endif // defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) |
| 13 | |
| 14 | #include "manifest_file.hpp" |
| 15 | |
| 16 | #ifdef OPENXR_HAVE_COMMON_CONFIG |
| 17 | #include "common_config.h" |
| 18 | #endif // OPENXR_HAVE_COMMON_CONFIG |
| 19 | |
| 20 | #include "filesystem_utils.hpp" |
| 21 | #include "loader_platform.hpp" |
| 22 | #include "platform_utils.hpp" |
| 23 | #include "loader_logger.hpp" |
| 24 | #include "unique_asset.h" |
| 25 | |
| 26 | #include <json/json.h> |
| 27 | #include <openxr/openxr.h> |
| 28 | |
| 29 | #include <algorithm> |
| 30 | #include <cstdlib> |
| 31 | #include <cstdio> |
| 32 | #include <cstring> |
| 33 | #include <fstream> |
| 34 | #include <memory> |
| 35 | #include <sstream> |
| 36 | #include <stdexcept> |
| 37 | #include <string> |
| 38 | #include <unordered_map> |
| 39 | #include <utility> |
| 40 | #include <vector> |
| 41 | |
| 42 | #ifndef FALLBACK_CONFIG_DIRS |
| 43 | #define FALLBACK_CONFIG_DIRS "/etc/xdg" |
| 44 | #endif // !FALLBACK_CONFIG_DIRS |
| 45 | |
| 46 | #ifndef FALLBACK_DATA_DIRS |
| 47 | #define FALLBACK_DATA_DIRS "/usr/local/share:/usr/share" |
| 48 | #endif // !FALLBACK_DATA_DIRS |
| 49 | |
| 50 | #ifndef SYSCONFDIR |
| 51 | #define SYSCONFDIR "/etc" |
| 52 | #endif // !SYSCONFDIR |
| 53 | |
| 54 | #ifdef XR_USE_PLATFORM_ANDROID |
| 55 | #include <android/asset_manager.h> |
| 56 | #endif |
| 57 | |
| 58 | #ifdef XRLOADER_DISABLE_EXCEPTION_HANDLING |
| 59 | #if JSON_USE_EXCEPTIONS |
| 60 | #error \ |
| 61 | "Loader is configured to not catch exceptions, but jsoncpp was built with exception-throwing enabled, which could violate the C ABI. One of those two things needs to change." |
| 62 | #endif // JSON_USE_EXCEPTIONS |
| 63 | #endif // !XRLOADER_DISABLE_EXCEPTION_HANDLING |
| 64 | |
| 65 | #include "runtime_interface.hpp" |
| 66 | |
| 67 | // Utility functions for finding files in the appropriate paths |
| 68 | |
| 69 | static inline bool StringEndsWith(const std::string &value, const std::string &ending) { |
| 70 | if (ending.size() > value.size()) { |
| 71 | return false; |
| 72 | } |
| 73 | return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); |
| 74 | } |
| 75 | |
| 76 | // If the file found is a manifest file name, add it to the out_files manifest list. |
| 77 | static void AddIfJson(const std::string &full_file, std::vector<std::string> &manifest_files) { |
| 78 | if (full_file.empty() || !StringEndsWith(full_file, ".json" )) { |
| 79 | return; |
| 80 | } |
| 81 | manifest_files.push_back(full_file); |
| 82 | } |
| 83 | |
| 84 | // Check the current path for any manifest files. If the provided search_path is a directory, look for |
| 85 | // all included JSON files in that directory. Otherwise, just check the provided search_path which should |
| 86 | // be a single filename. |
| 87 | static void CheckAllFilesInThePath(const std::string &search_path, bool is_directory_list, |
| 88 | std::vector<std::string> &manifest_files) { |
| 89 | if (FileSysUtilsPathExists(search_path)) { |
| 90 | std::string absolute_path; |
| 91 | if (!is_directory_list) { |
| 92 | // If the file exists, try to add it |
| 93 | if (FileSysUtilsIsRegularFile(search_path)) { |
| 94 | FileSysUtilsGetAbsolutePath(search_path, absolute_path); |
| 95 | AddIfJson(absolute_path, manifest_files); |
| 96 | } |
| 97 | } else { |
| 98 | std::vector<std::string> files; |
| 99 | if (FileSysUtilsFindFilesInPath(search_path, files)) { |
| 100 | for (std::string &cur_file : files) { |
| 101 | std::string relative_path; |
| 102 | FileSysUtilsCombinePaths(search_path, cur_file, relative_path); |
| 103 | if (!FileSysUtilsGetAbsolutePath(relative_path, absolute_path)) { |
| 104 | continue; |
| 105 | } |
| 106 | AddIfJson(absolute_path, manifest_files); |
| 107 | } |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | // Add all manifest files in the provided paths to the manifest_files list. If search_path |
| 114 | // is made up of directory listings (versus direct manifest file names) search each path for |
| 115 | // any manifest files. |
| 116 | static void AddFilesInPath(const std::string &search_path, bool is_directory_list, std::vector<std::string> &manifest_files) { |
| 117 | std::size_t last_found = 0; |
| 118 | std::size_t found = search_path.find_first_of(PATH_SEPARATOR); |
| 119 | std::string cur_search; |
| 120 | |
| 121 | // Handle any path listings in the string (separated by the appropriate path separator) |
| 122 | while (found != std::string::npos) { |
| 123 | // substr takes a start index and length. |
| 124 | std::size_t length = found - last_found; |
| 125 | cur_search = search_path.substr(last_found, length); |
| 126 | |
| 127 | CheckAllFilesInThePath(cur_search, is_directory_list, manifest_files); |
| 128 | |
| 129 | // This works around issue if multiple path separator follow each other directly. |
| 130 | last_found = found; |
| 131 | while (found == last_found) { |
| 132 | last_found = found + 1; |
| 133 | found = search_path.find_first_of(PATH_SEPARATOR, last_found); |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | // If there's something remaining in the string, copy it over |
| 138 | if (last_found < search_path.size()) { |
| 139 | cur_search = search_path.substr(last_found); |
| 140 | CheckAllFilesInThePath(cur_search, is_directory_list, manifest_files); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | // Copy all paths listed in the cur_path string into output_path and append the appropriate relative_path onto the end of each. |
| 145 | static void CopyIncludedPaths(bool is_directory_list, const std::string &cur_path, const std::string &relative_path, |
| 146 | std::string &output_path) { |
| 147 | if (!cur_path.empty()) { |
| 148 | std::size_t last_found = 0; |
| 149 | std::size_t found = cur_path.find_first_of(PATH_SEPARATOR); |
| 150 | |
| 151 | // Handle any path listings in the string (separated by the appropriate path separator) |
| 152 | while (found != std::string::npos) { |
| 153 | std::size_t length = found - last_found; |
| 154 | output_path += cur_path.substr(last_found, length); |
| 155 | if (is_directory_list && (cur_path[found - 1] != '\\' && cur_path[found - 1] != '/')) { |
| 156 | output_path += DIRECTORY_SYMBOL; |
| 157 | } |
| 158 | output_path += relative_path; |
| 159 | output_path += PATH_SEPARATOR; |
| 160 | |
| 161 | last_found = found; |
| 162 | found = cur_path.find_first_of(PATH_SEPARATOR, found + 1); |
| 163 | } |
| 164 | |
| 165 | // If there's something remaining in the string, copy it over |
| 166 | size_t last_char = cur_path.size() - 1; |
| 167 | if (last_found != last_char) { |
| 168 | output_path += cur_path.substr(last_found); |
| 169 | if (is_directory_list && (cur_path[last_char] != '\\' && cur_path[last_char] != '/')) { |
| 170 | output_path += DIRECTORY_SYMBOL; |
| 171 | } |
| 172 | output_path += relative_path; |
| 173 | output_path += PATH_SEPARATOR; |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | // Look for data files in the provided paths, but first check the environment override to determine if we should use that instead. |
| 179 | static void ReadDataFilesInSearchPaths(const std::string &override_env_var, const std::string &relative_path, bool &override_active, |
| 180 | std::vector<std::string> &manifest_files) { |
| 181 | std::string override_path; |
| 182 | std::string search_path; |
| 183 | |
| 184 | if (!override_env_var.empty()) { |
| 185 | bool permit_override = true; |
| 186 | #ifndef XR_OS_WINDOWS |
| 187 | if (geteuid() != getuid() || getegid() != getgid()) { |
| 188 | // Don't allow setuid apps to use the env var |
| 189 | permit_override = false; |
| 190 | } |
| 191 | #endif |
| 192 | if (permit_override) { |
| 193 | override_path = PlatformUtilsGetSecureEnv(override_env_var.c_str()); |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | if (!override_path.empty()) { |
| 198 | CopyIncludedPaths(true, override_path, "" , search_path); |
| 199 | override_active = true; |
| 200 | } else { |
| 201 | override_active = false; |
| 202 | #if !defined(XR_OS_WINDOWS) && !defined(XR_OS_ANDROID) |
| 203 | const char home_additional[] = ".local/share/" ; |
| 204 | |
| 205 | // Determine how much space is needed to generate the full search path |
| 206 | // for the current manifest files. |
| 207 | std::string xdg_conf_dirs = PlatformUtilsGetSecureEnv("XDG_CONFIG_DIRS" ); |
| 208 | std::string xdg_data_dirs = PlatformUtilsGetSecureEnv("XDG_DATA_DIRS" ); |
| 209 | std::string xdg_data_home = PlatformUtilsGetSecureEnv("XDG_DATA_HOME" ); |
| 210 | std::string home = PlatformUtilsGetSecureEnv("HOME" ); |
| 211 | |
| 212 | if (xdg_conf_dirs.empty()) { |
| 213 | CopyIncludedPaths(true, FALLBACK_CONFIG_DIRS, relative_path, search_path); |
| 214 | } else { |
| 215 | CopyIncludedPaths(true, xdg_conf_dirs, relative_path, search_path); |
| 216 | } |
| 217 | |
| 218 | CopyIncludedPaths(true, SYSCONFDIR, relative_path, search_path); |
| 219 | #if defined(EXTRASYSCONFDIR) |
| 220 | CopyIncludedPaths(true, EXTRASYSCONFDIR, relative_path, search_path); |
| 221 | #endif |
| 222 | |
| 223 | if (xdg_data_dirs.empty()) { |
| 224 | CopyIncludedPaths(true, FALLBACK_DATA_DIRS, relative_path, search_path); |
| 225 | } else { |
| 226 | CopyIncludedPaths(true, xdg_data_dirs, relative_path, search_path); |
| 227 | } |
| 228 | |
| 229 | if (!xdg_data_home.empty()) { |
| 230 | CopyIncludedPaths(true, xdg_data_home, relative_path, search_path); |
| 231 | } else if (!home.empty()) { |
| 232 | std::string relative_home_path = home_additional; |
| 233 | relative_home_path += relative_path; |
| 234 | CopyIncludedPaths(true, home, relative_home_path, search_path); |
| 235 | } |
| 236 | #elif defined(XR_OS_ANDROID) |
| 237 | CopyIncludedPaths(true, "/product/etc" , relative_path, search_path); |
| 238 | CopyIncludedPaths(true, "/odm/etc" , relative_path, search_path); |
| 239 | CopyIncludedPaths(true, "/oem/etc" , relative_path, search_path); |
| 240 | CopyIncludedPaths(true, "/vendor/etc" , relative_path, search_path); |
| 241 | CopyIncludedPaths(true, "/system/etc" , relative_path, search_path); |
| 242 | #else |
| 243 | (void)relative_path; |
| 244 | #endif |
| 245 | } |
| 246 | |
| 247 | // Now, parse the paths and add any manifest files found in them. |
| 248 | AddFilesInPath(search_path, true, manifest_files); |
| 249 | } |
| 250 | |
| 251 | #ifdef XR_OS_LINUX |
| 252 | |
| 253 | // Get an XDG environment variable with a $HOME-relative default |
| 254 | static std::string GetXDGEnvHome(const char *name, const char *fallback_path) { |
| 255 | std::string result = PlatformUtilsGetSecureEnv(name); |
| 256 | if (!result.empty()) { |
| 257 | return result; |
| 258 | } |
| 259 | result = PlatformUtilsGetSecureEnv("HOME" ); |
| 260 | if (result.empty()) { |
| 261 | return result; |
| 262 | } |
| 263 | result += "/" ; |
| 264 | result += fallback_path; |
| 265 | return result; |
| 266 | } |
| 267 | |
| 268 | // Get an XDG environment variable with absolute defaults |
| 269 | static std::string GetXDGEnvAbsolute(const char *name, const char *fallback_paths) { |
| 270 | std::string result = PlatformUtilsGetSecureEnv(name); |
| 271 | if (!result.empty()) { |
| 272 | return result; |
| 273 | } |
| 274 | return fallback_paths; |
| 275 | } |
| 276 | |
| 277 | // Return the first instance of relative_path occurring in an XDG config dir according to standard |
| 278 | // precedence order. |
| 279 | static bool FindXDGConfigFile(const std::string &relative_path, std::string &out) { |
| 280 | out = GetXDGEnvHome("XDG_CONFIG_HOME" , ".config" ); |
| 281 | if (!out.empty()) { |
| 282 | out += "/" ; |
| 283 | out += relative_path; |
| 284 | |
| 285 | LoaderLogger::LogInfoMessage("" , "Looking for " + relative_path + " in XDG_CONFIG_HOME: " + out); |
| 286 | if (FileSysUtilsPathExists(out)) { |
| 287 | return true; |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | std::istringstream iss(GetXDGEnvAbsolute("XDG_CONFIG_DIRS" , FALLBACK_CONFIG_DIRS)); |
| 292 | std::string path; |
| 293 | while (std::getline(iss, path, PATH_SEPARATOR)) { |
| 294 | if (path.empty()) { |
| 295 | continue; |
| 296 | } |
| 297 | out = path; |
| 298 | out += "/" ; |
| 299 | out += relative_path; |
| 300 | LoaderLogger::LogInfoMessage("" , "Looking for " + relative_path + " in an entry of XDG_CONFIG_DIRS: " + out); |
| 301 | if (FileSysUtilsPathExists(out)) { |
| 302 | return true; |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | out = SYSCONFDIR; |
| 307 | out += "/" ; |
| 308 | out += relative_path; |
| 309 | LoaderLogger::LogInfoMessage("" , "Looking for " + relative_path + " in compiled-in SYSCONFDIR: " + out); |
| 310 | if (FileSysUtilsPathExists(out)) { |
| 311 | return true; |
| 312 | } |
| 313 | |
| 314 | #if defined(EXTRASYSCONFDIR) |
| 315 | out = EXTRASYSCONFDIR; |
| 316 | out += "/" ; |
| 317 | out += relative_path; |
| 318 | LoaderLogger::LogInfoMessage("" , "Looking for " + relative_path + " in compiled-in EXTRASYSCONFDIR: " + out); |
| 319 | if (FileSysUtilsPathExists(out)) { |
| 320 | return true; |
| 321 | } |
| 322 | #endif |
| 323 | |
| 324 | out.clear(); |
| 325 | return false; |
| 326 | } |
| 327 | |
| 328 | #endif |
| 329 | |
| 330 | #ifdef XR_OS_WINDOWS |
| 331 | |
| 332 | // Look for runtime data files in the provided paths, but first check the environment override to determine |
| 333 | // if we should use that instead. |
| 334 | static void ReadRuntimeDataFilesInRegistry(const std::string &runtime_registry_location, |
| 335 | const std::string &default_runtime_value_name, |
| 336 | std::vector<std::string> &manifest_files) { |
| 337 | HKEY hkey; |
| 338 | DWORD access_flags; |
| 339 | wchar_t value_w[1024]; |
| 340 | DWORD value_size_w = sizeof(value_w); // byte size of the buffer. |
| 341 | |
| 342 | // Generate the full registry location for the registry information |
| 343 | std::string full_registry_location = OPENXR_REGISTRY_LOCATION; |
| 344 | full_registry_location += std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)); |
| 345 | full_registry_location += runtime_registry_location; |
| 346 | |
| 347 | const std::wstring full_registry_location_w = utf8_to_wide(full_registry_location); |
| 348 | const std::wstring default_runtime_value_name_w = utf8_to_wide(default_runtime_value_name); |
| 349 | |
| 350 | // Use 64 bit regkey for 64bit application, and use 32 bit regkey in WOW for 32bit application. |
| 351 | access_flags = KEY_QUERY_VALUE; |
| 352 | LONG open_value = RegOpenKeyExW(HKEY_LOCAL_MACHINE, full_registry_location_w.c_str(), 0, access_flags, &hkey); |
| 353 | |
| 354 | if (ERROR_SUCCESS != open_value) { |
| 355 | LoaderLogger::LogWarningMessage("" , |
| 356 | "ReadRuntimeDataFilesInRegistry - failed to open registry key " + full_registry_location); |
| 357 | |
| 358 | return; |
| 359 | } |
| 360 | |
| 361 | if (ERROR_SUCCESS != RegGetValueW(hkey, nullptr, default_runtime_value_name_w.c_str(), |
| 362 | RRF_RT_REG_SZ | REG_EXPAND_SZ | RRF_ZEROONFAILURE, NULL, reinterpret_cast<LPBYTE>(&value_w), |
| 363 | &value_size_w)) { |
| 364 | LoaderLogger::LogWarningMessage( |
| 365 | "" , "ReadRuntimeDataFilesInRegistry - failed to read registry value " + default_runtime_value_name); |
| 366 | } else { |
| 367 | AddFilesInPath(wide_to_utf8(value_w), false, manifest_files); |
| 368 | } |
| 369 | |
| 370 | RegCloseKey(hkey); |
| 371 | } |
| 372 | |
| 373 | // Look for layer data files in the provided paths, but first check the environment override to determine |
| 374 | // if we should use that instead. |
| 375 | static void ReadLayerDataFilesInRegistry(const std::string ®istry_location, std::vector<std::string> &manifest_files) { |
| 376 | const std::wstring full_registry_location_w = |
| 377 | utf8_to_wide(OPENXR_REGISTRY_LOCATION + std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) + registry_location); |
| 378 | |
| 379 | auto ReadLayerDataFilesInHive = [&](HKEY hive) { |
| 380 | HKEY hkey; |
| 381 | LONG open_value = RegOpenKeyExW(hive, full_registry_location_w.c_str(), 0, KEY_QUERY_VALUE, &hkey); |
| 382 | if (ERROR_SUCCESS != open_value) { |
| 383 | return false; |
| 384 | } |
| 385 | |
| 386 | wchar_t name_w[1024]{}; |
| 387 | LONG rtn_value; |
| 388 | DWORD name_size = 1023; |
| 389 | DWORD value; |
| 390 | DWORD value_size = sizeof(value); |
| 391 | DWORD key_index = 0; |
| 392 | while (ERROR_SUCCESS == |
| 393 | (rtn_value = RegEnumValueW(hkey, key_index++, name_w, &name_size, NULL, NULL, (LPBYTE)&value, &value_size))) { |
| 394 | if (value_size == sizeof(value) && value == 0) { |
| 395 | const std::string filename = wide_to_utf8(name_w); |
| 396 | AddFilesInPath(filename, false, manifest_files); |
| 397 | } |
| 398 | // Reset some items for the next loop |
| 399 | name_size = 1023; |
| 400 | } |
| 401 | |
| 402 | RegCloseKey(hkey); |
| 403 | |
| 404 | return true; |
| 405 | }; |
| 406 | |
| 407 | // Do not allow high integrity processes to act on data that can be controlled by medium integrity processes. |
| 408 | const bool readFromCurrentUser = !IsHighIntegrityLevel(); |
| 409 | |
| 410 | bool found = ReadLayerDataFilesInHive(HKEY_LOCAL_MACHINE); |
| 411 | if (readFromCurrentUser) { |
| 412 | found |= ReadLayerDataFilesInHive(HKEY_CURRENT_USER); |
| 413 | } |
| 414 | |
| 415 | if (!found) { |
| 416 | std::string warning_message = "ReadLayerDataFilesInRegistry - failed to read registry location " ; |
| 417 | warning_message += registry_location; |
| 418 | warning_message += (readFromCurrentUser ? " in either HKEY_LOCAL_MACHINE or HKEY_CURRENT_USER" : " in HKEY_LOCAL_MACHINE" ); |
| 419 | LoaderLogger::LogWarningMessage("" , warning_message); |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | #endif // XR_OS_WINDOWS |
| 424 | |
| 425 | ManifestFile::ManifestFile(ManifestFileType type, const std::string &filename, const std::string &library_path) |
| 426 | : _filename(filename), _type(type), _library_path(library_path) {} |
| 427 | |
| 428 | bool ManifestFile::IsValidJson(const Json::Value &root_node, JsonVersion &version) { |
| 429 | if (root_node["file_format_version" ].isNull() || !root_node["file_format_version" ].isString()) { |
| 430 | LoaderLogger::LogErrorMessage("" , "ManifestFile::IsValidJson - JSON file missing \"file_format_version\"" ); |
| 431 | return false; |
| 432 | } |
| 433 | std::string file_format = root_node["file_format_version" ].asString(); |
| 434 | const int num_fields = sscanf(file_format.c_str(), "%u.%u.%u" , &version.major, &version.minor, &version.patch); |
| 435 | |
| 436 | // Only version 1.0.0 is defined currently. Eventually we may have more version, but |
| 437 | // some of the versions may only be valid for layers or runtimes specifically. |
| 438 | if (num_fields != 3 || version.major != 1 || version.minor != 0 || version.patch != 0) { |
| 439 | std::ostringstream error_ss; |
| 440 | error_ss << "ManifestFile::IsValidJson - JSON \"file_format_version\" " << version.major << "." << version.minor << "." |
| 441 | << version.patch << " is not supported" ; |
| 442 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 443 | return false; |
| 444 | } |
| 445 | |
| 446 | return true; |
| 447 | } |
| 448 | |
| 449 | static void GetExtensionProperties(const std::vector<ExtensionListing> &extensions, std::vector<XrExtensionProperties> &props) { |
| 450 | for (const auto &ext : extensions) { |
| 451 | auto it = |
| 452 | std::find_if(props.begin(), props.end(), [&](XrExtensionProperties &prop) { return prop.extensionName == ext.name; }); |
| 453 | if (it != props.end()) { |
| 454 | it->extensionVersion = std::max(it->extensionVersion, ext.extension_version); |
| 455 | } else { |
| 456 | XrExtensionProperties prop{}; |
| 457 | prop.type = XR_TYPE_EXTENSION_PROPERTIES; |
| 458 | strncpy(prop.extensionName, ext.name.c_str(), XR_MAX_EXTENSION_NAME_SIZE - 1); |
| 459 | prop.extensionName[XR_MAX_EXTENSION_NAME_SIZE - 1] = '\0'; |
| 460 | prop.extensionVersion = ext.extension_version; |
| 461 | props.push_back(prop); |
| 462 | } |
| 463 | } |
| 464 | } |
| 465 | |
| 466 | // Return any instance extensions found in the manifest files in the proper form for |
| 467 | // OpenXR (XrExtensionProperties). |
| 468 | void ManifestFile::GetInstanceExtensionProperties(std::vector<XrExtensionProperties> &props) { |
| 469 | GetExtensionProperties(_instance_extensions, props); |
| 470 | } |
| 471 | |
| 472 | const std::string &ManifestFile::GetFunctionName(const std::string &func_name) const { |
| 473 | if (!_functions_renamed.empty()) { |
| 474 | auto found = _functions_renamed.find(func_name); |
| 475 | if (found != _functions_renamed.end()) { |
| 476 | return found->second; |
| 477 | } |
| 478 | } |
| 479 | return func_name; |
| 480 | } |
| 481 | |
| 482 | RuntimeManifestFile::RuntimeManifestFile(const std::string &filename, const std::string &library_path) |
| 483 | : ManifestFile(MANIFEST_TYPE_RUNTIME, filename, library_path) {} |
| 484 | |
| 485 | static void ParseExtension(Json::Value const &ext, std::vector<ExtensionListing> &extensions) { |
| 486 | Json::Value ext_name = ext["name" ]; |
| 487 | Json::Value ext_version = ext["extension_version" ]; |
| 488 | |
| 489 | // Allow "extension_version" as a String or a UInt to maintain backwards compatibility, even though it should be a String. |
| 490 | // Internal Issue 1411: https://gitlab.khronos.org/openxr/openxr/-/issues/1411 |
| 491 | // Internal MR !1867: https://gitlab.khronos.org/openxr/openxr/-/merge_requests/1867 |
| 492 | if (ext_name.isString() && (ext_version.isString() || ext_version.isUInt())) { |
| 493 | ExtensionListing ext_listing = {}; |
| 494 | ext_listing.name = ext_name.asString(); |
| 495 | if (ext_version.isUInt()) { |
| 496 | ext_listing.extension_version = ext_version.asUInt(); |
| 497 | } else { |
| 498 | ext_listing.extension_version = atoi(ext_version.asString().c_str()); |
| 499 | } |
| 500 | extensions.push_back(ext_listing); |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | void ManifestFile::ParseCommon(Json::Value const &root_node) { |
| 505 | const Json::Value &inst_exts = root_node["instance_extensions" ]; |
| 506 | if (!inst_exts.isNull() && inst_exts.isArray()) { |
| 507 | for (const auto &ext : inst_exts) { |
| 508 | ParseExtension(ext, _instance_extensions); |
| 509 | } |
| 510 | } |
| 511 | const Json::Value &funcs_renamed = root_node["functions" ]; |
| 512 | if (!funcs_renamed.isNull() && !funcs_renamed.empty()) { |
| 513 | for (Json::ValueConstIterator func_it = funcs_renamed.begin(); func_it != funcs_renamed.end(); ++func_it) { |
| 514 | if (!(*func_it).isString()) { |
| 515 | LoaderLogger::LogWarningMessage( |
| 516 | "" , "ManifestFile::ParseCommon " + _filename + " \"functions\" section contains non-string values." ); |
| 517 | continue; |
| 518 | } |
| 519 | std::string original_name = func_it.key().asString(); |
| 520 | std::string new_name = (*func_it).asString(); |
| 521 | _functions_renamed.emplace(original_name, new_name); |
| 522 | } |
| 523 | } |
| 524 | } |
| 525 | |
| 526 | void RuntimeManifestFile::CreateIfValid(std::string const &filename, |
| 527 | std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) { |
| 528 | std::ifstream json_stream(filename, std::ifstream::in); |
| 529 | |
| 530 | LoaderLogger::LogInfoMessage("" , "RuntimeManifestFile::CreateIfValid - attempting to load " + filename); |
| 531 | std::ostringstream error_ss("RuntimeManifestFile::CreateIfValid " ); |
| 532 | if (!json_stream.is_open()) { |
| 533 | error_ss << "failed to open " << filename << ". Does it exist?" ; |
| 534 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 535 | return; |
| 536 | } |
| 537 | Json::CharReaderBuilder builder; |
| 538 | std::string errors; |
| 539 | Json::Value root_node = Json::nullValue; |
| 540 | if (!Json::parseFromStream(builder, json_stream, &root_node, &errors) || !root_node.isObject()) { |
| 541 | error_ss << "failed to parse " << filename << "." ; |
| 542 | if (!errors.empty()) { |
| 543 | error_ss << " (Error message: " << errors << ")" ; |
| 544 | } |
| 545 | error_ss << " Is it a valid runtime manifest file?" ; |
| 546 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 547 | return; |
| 548 | } |
| 549 | |
| 550 | CreateIfValid(root_node, filename, manifest_files); |
| 551 | } |
| 552 | |
| 553 | void RuntimeManifestFile::CreateIfValid(const Json::Value &root_node, const std::string &filename, |
| 554 | std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) { |
| 555 | std::ostringstream error_ss("RuntimeManifestFile::CreateIfValid " ); |
| 556 | JsonVersion file_version = {}; |
| 557 | if (!ManifestFile::IsValidJson(root_node, file_version)) { |
| 558 | error_ss << "isValidJson indicates " << filename << " is not a valid manifest file." ; |
| 559 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 560 | return; |
| 561 | } |
| 562 | const Json::Value &runtime_root_node = root_node["runtime" ]; |
| 563 | // The Runtime manifest file needs the "runtime" root as well as a sub-node for "library_path". If any of those aren't there, |
| 564 | // fail. |
| 565 | if (runtime_root_node.isNull() || runtime_root_node["library_path" ].isNull() || !runtime_root_node["library_path" ].isString()) { |
| 566 | error_ss << filename << " is missing required fields. Verify all proper fields exist." ; |
| 567 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 568 | return; |
| 569 | } |
| 570 | |
| 571 | std::string lib_path = runtime_root_node["library_path" ].asString(); |
| 572 | |
| 573 | // If the library_path variable has no directory symbol, it's just a file name and should be accessible on the |
| 574 | // global library path. |
| 575 | if (lib_path.find('\\') != std::string::npos || lib_path.find('/') != std::string::npos) { |
| 576 | // If the library_path is an absolute path, just use that if it exists |
| 577 | if (FileSysUtilsIsAbsolutePath(lib_path)) { |
| 578 | if (!FileSysUtilsPathExists(lib_path)) { |
| 579 | error_ss << filename << " library " << lib_path << " does not appear to exist" ; |
| 580 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 581 | return; |
| 582 | } |
| 583 | } else { |
| 584 | // Otherwise, treat the library path as a relative path based on the JSON file. |
| 585 | std::string canonical_path; |
| 586 | std::string combined_path; |
| 587 | std::string file_parent; |
| 588 | // Search relative to the real manifest file, not relative to the symlink |
| 589 | if (!FileSysUtilsGetCanonicalPath(filename, canonical_path)) { |
| 590 | // Give relative to the non-canonical path a chance |
| 591 | canonical_path = filename; |
| 592 | } |
| 593 | if (!FileSysUtilsGetParentPath(canonical_path, file_parent) || |
| 594 | !FileSysUtilsCombinePaths(file_parent, lib_path, combined_path) || !FileSysUtilsPathExists(combined_path)) { |
| 595 | error_ss << filename << " library " << combined_path << " does not appear to exist" ; |
| 596 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 597 | return; |
| 598 | } |
| 599 | lib_path = combined_path; |
| 600 | } |
| 601 | } |
| 602 | |
| 603 | // Add this runtime manifest file |
| 604 | manifest_files.emplace_back(new RuntimeManifestFile(filename, lib_path)); |
| 605 | |
| 606 | // Add any extensions to it after the fact. |
| 607 | // Handle any renamed functions |
| 608 | manifest_files.back()->ParseCommon(runtime_root_node); |
| 609 | } |
| 610 | |
| 611 | // Find all manifest files in the appropriate search paths/registries for the given type. |
| 612 | XrResult RuntimeManifestFile::FindManifestFiles(std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) { |
| 613 | XrResult result = XR_SUCCESS; |
| 614 | std::string filename = PlatformUtilsGetSecureEnv(OPENXR_RUNTIME_JSON_ENV_VAR); |
| 615 | if (!filename.empty()) { |
| 616 | LoaderLogger::LogInfoMessage( |
| 617 | "" , "RuntimeManifestFile::FindManifestFiles - using environment variable override runtime file " + filename); |
| 618 | } else { |
| 619 | #ifdef XR_OS_WINDOWS |
| 620 | std::vector<std::string> filenames; |
| 621 | ReadRuntimeDataFilesInRegistry("" , "ActiveRuntime" , filenames); |
| 622 | if (filenames.size() == 0) { |
| 623 | LoaderLogger::LogErrorMessage( |
| 624 | "" , "RuntimeManifestFile::FindManifestFiles - failed to find active runtime file in registry" ); |
| 625 | return XR_ERROR_RUNTIME_UNAVAILABLE; |
| 626 | } |
| 627 | if (filenames.size() > 1) { |
| 628 | LoaderLogger::LogWarningMessage( |
| 629 | "" , "RuntimeManifestFile::FindManifestFiles - found too many default runtime files in registry" ); |
| 630 | } |
| 631 | filename = filenames[0]; |
| 632 | LoaderLogger::LogInfoMessage("" , |
| 633 | "RuntimeManifestFile::FindManifestFiles - using registry-specified runtime file " + filename); |
| 634 | #elif defined(XR_OS_LINUX) |
| 635 | const std::string relative_path = |
| 636 | "openxr/" + std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) + "/active_runtime.json" ; |
| 637 | if (!FindXDGConfigFile(relative_path, filename)) { |
| 638 | LoaderLogger::LogErrorMessage( |
| 639 | "" , "RuntimeManifestFile::FindManifestFiles - failed to determine active runtime file path for this environment" ); |
| 640 | return XR_ERROR_RUNTIME_UNAVAILABLE; |
| 641 | } |
| 642 | #else |
| 643 | |
| 644 | #if defined(XR_KHR_LOADER_INIT_SUPPORT) |
| 645 | Json::Value virtualManifest; |
| 646 | result = GetPlatformRuntimeVirtualManifest(virtualManifest); |
| 647 | if (XR_SUCCESS == result) { |
| 648 | RuntimeManifestFile::CreateIfValid(virtualManifest, "" , manifest_files); |
| 649 | return result; |
| 650 | } |
| 651 | #endif // defined(XR_KHR_LOADER_INIT_SUPPORT) |
| 652 | if (!PlatformGetGlobalRuntimeFileName(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), filename)) { |
| 653 | LoaderLogger::LogErrorMessage( |
| 654 | "" , "RuntimeManifestFile::FindManifestFiles - failed to determine active runtime file path for this environment" ); |
| 655 | return XR_ERROR_RUNTIME_UNAVAILABLE; |
| 656 | } |
| 657 | result = XR_SUCCESS; |
| 658 | LoaderLogger::LogInfoMessage("" , "RuntimeManifestFile::FindManifestFiles - using global runtime file " + filename); |
| 659 | #endif |
| 660 | } |
| 661 | RuntimeManifestFile::CreateIfValid(filename, manifest_files); |
| 662 | |
| 663 | return result; |
| 664 | } |
| 665 | |
| 666 | ApiLayerManifestFile::ApiLayerManifestFile(ManifestFileType type, const std::string &filename, const std::string &layer_name, |
| 667 | const std::string &description, const JsonVersion &api_version, |
| 668 | const uint32_t &implementation_version, const std::string &library_path) |
| 669 | : ManifestFile(type, filename, library_path), |
| 670 | _api_version(api_version), |
| 671 | _layer_name(layer_name), |
| 672 | _description(description), |
| 673 | _implementation_version(implementation_version) {} |
| 674 | |
| 675 | #ifdef XR_USE_PLATFORM_ANDROID |
| 676 | void ApiLayerManifestFile::AddManifestFilesAndroid(ManifestFileType type, |
| 677 | std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) { |
| 678 | AAssetManager *assetManager = (AAssetManager *)Android_Get_Asset_Manager(); |
| 679 | std::vector<std::string> filenames; |
| 680 | { |
| 681 | std::string search_path = "" ; |
| 682 | switch (type) { |
| 683 | case MANIFEST_TYPE_IMPLICIT_API_LAYER: |
| 684 | search_path = "openxr/1/api_layers/implicit.d/" ; |
| 685 | break; |
| 686 | case MANIFEST_TYPE_EXPLICIT_API_LAYER: |
| 687 | search_path = "openxr/1/api_layers/explicit.d/" ; |
| 688 | break; |
| 689 | default: |
| 690 | return; |
| 691 | } |
| 692 | |
| 693 | UniqueAssetDir dir{AAssetManager_openDir(assetManager, search_path.c_str())}; |
| 694 | if (!dir) { |
| 695 | return; |
| 696 | } |
| 697 | const std::string json = ".json" ; |
| 698 | const char *fn = nullptr; |
| 699 | while ((fn = AAssetDir_getNextFileName(dir.get())) != nullptr) { |
| 700 | const std::string filename = search_path + fn; |
| 701 | if (filename.size() < json.size()) { |
| 702 | continue; |
| 703 | } |
| 704 | if (filename.compare(filename.size() - json.size(), json.size(), json) == 0) { |
| 705 | filenames.push_back(filename); |
| 706 | } |
| 707 | } |
| 708 | } |
| 709 | for (const auto &filename : filenames) { |
| 710 | UniqueAsset asset{AAssetManager_open(assetManager, filename.c_str(), AASSET_MODE_BUFFER)}; |
| 711 | if (!asset) { |
| 712 | LoaderLogger::LogWarningMessage( |
| 713 | "" , "ApiLayerManifestFile::AddManifestFilesAndroid unable to open asset " + filename + ", skipping" ); |
| 714 | |
| 715 | continue; |
| 716 | } |
| 717 | size_t length = AAsset_getLength(asset.get()); |
| 718 | const char *buf = reinterpret_cast<const char *>(AAsset_getBuffer(asset.get())); |
| 719 | if (!buf) { |
| 720 | LoaderLogger::LogWarningMessage( |
| 721 | "" , "ApiLayerManifestFile::AddManifestFilesAndroid unable to access asset" + filename + ", skipping" ); |
| 722 | |
| 723 | continue; |
| 724 | } |
| 725 | std::istringstream json_stream(std::string{buf, length}); |
| 726 | |
| 727 | CreateIfValid(ManifestFileType::MANIFEST_TYPE_EXPLICIT_API_LAYER, filename, json_stream, |
| 728 | &ApiLayerManifestFile::LocateLibraryInAssets, manifest_files); |
| 729 | } |
| 730 | } |
| 731 | #endif // XR_USE_PLATFORM_ANDROID |
| 732 | |
| 733 | void ApiLayerManifestFile::CreateIfValid(ManifestFileType type, const std::string &filename, std::istream &json_stream, |
| 734 | LibraryLocator locate_library, |
| 735 | std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) { |
| 736 | std::ostringstream error_ss("ApiLayerManifestFile::CreateIfValid " ); |
| 737 | Json::CharReaderBuilder builder; |
| 738 | std::string errors; |
| 739 | Json::Value root_node = Json::nullValue; |
| 740 | if (!Json::parseFromStream(builder, json_stream, &root_node, &errors) || !root_node.isObject()) { |
| 741 | error_ss << "failed to parse " << filename << "." ; |
| 742 | if (!errors.empty()) { |
| 743 | error_ss << " (Error message: " << errors << ")" ; |
| 744 | } |
| 745 | error_ss << " Is it a valid layer manifest file?" ; |
| 746 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 747 | return; |
| 748 | } |
| 749 | JsonVersion file_version = {}; |
| 750 | if (!ManifestFile::IsValidJson(root_node, file_version)) { |
| 751 | error_ss << "isValidJson indicates " << filename << " is not a valid manifest file." ; |
| 752 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 753 | return; |
| 754 | } |
| 755 | |
| 756 | Json::Value layer_root_node = root_node["api_layer" ]; |
| 757 | |
| 758 | // The API Layer manifest file needs the "api_layer" root as well as other sub-nodes. |
| 759 | // If any of those aren't there, fail. |
| 760 | if (layer_root_node.isNull() || layer_root_node["name" ].isNull() || !layer_root_node["name" ].isString() || |
| 761 | layer_root_node["api_version" ].isNull() || !layer_root_node["api_version" ].isString() || |
| 762 | layer_root_node["library_path" ].isNull() || !layer_root_node["library_path" ].isString() || |
| 763 | layer_root_node["implementation_version" ].isNull() || !layer_root_node["implementation_version" ].isString()) { |
| 764 | error_ss << filename << " is missing required fields. Verify all proper fields exist." ; |
| 765 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 766 | return; |
| 767 | } |
| 768 | if (MANIFEST_TYPE_IMPLICIT_API_LAYER == type) { |
| 769 | bool enabled = true; |
| 770 | // Implicit layers require the disable environment variable. |
| 771 | if (layer_root_node["disable_environment" ].isNull() || !layer_root_node["disable_environment" ].isString()) { |
| 772 | error_ss << "Implicit layer " << filename << " is missing \"disable_environment\"" ; |
| 773 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 774 | return; |
| 775 | } |
| 776 | // Check if there's an enable environment variable provided |
| 777 | if (!layer_root_node["enable_environment" ].isNull() && layer_root_node["enable_environment" ].isString()) { |
| 778 | std::string env_var = layer_root_node["enable_environment" ].asString(); |
| 779 | // If it's not set in the environment, disable the layer |
| 780 | if (!PlatformUtilsGetEnvSet(env_var.c_str())) { |
| 781 | enabled = false; |
| 782 | } |
| 783 | } |
| 784 | // Check for the disable environment variable, which must be provided in the JSON |
| 785 | std::string env_var = layer_root_node["disable_environment" ].asString(); |
| 786 | // If the env var is set, disable the layer. Disable env var overrides enable above |
| 787 | if (PlatformUtilsGetEnvSet(env_var.c_str())) { |
| 788 | enabled = false; |
| 789 | } |
| 790 | |
| 791 | // Not enabled, so pretend like it isn't even there. |
| 792 | if (!enabled) { |
| 793 | error_ss << "Implicit layer " << filename << " is disabled" ; |
| 794 | LoaderLogger::LogInfoMessage("" , error_ss.str()); |
| 795 | return; |
| 796 | } |
| 797 | } |
| 798 | std::string layer_name = layer_root_node["name" ].asString(); |
| 799 | std::string api_version_string = layer_root_node["api_version" ].asString(); |
| 800 | JsonVersion api_version = {}; |
| 801 | const int num_fields = sscanf(api_version_string.c_str(), "%u.%u" , &api_version.major, &api_version.minor); |
| 802 | api_version.patch = 0; |
| 803 | |
| 804 | if ((num_fields != 2) || (api_version.major == 0 && api_version.minor == 0) || |
| 805 | api_version.major > XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) { |
| 806 | error_ss << "layer " << filename << " has invalid API Version. Skipping layer." ; |
| 807 | LoaderLogger::LogWarningMessage("" , error_ss.str()); |
| 808 | return; |
| 809 | } |
| 810 | |
| 811 | uint32_t implementation_version = atoi(layer_root_node["implementation_version" ].asString().c_str()); |
| 812 | std::string library_path = layer_root_node["library_path" ].asString(); |
| 813 | |
| 814 | // If the library_path variable has no directory symbol, it's just a file name and should be accessible on the |
| 815 | // global library path. |
| 816 | if (library_path.find('\\') != std::string::npos || library_path.find('/') != std::string::npos) { |
| 817 | // If the library_path is an absolute path, just use that if it exists |
| 818 | if (FileSysUtilsIsAbsolutePath(library_path)) { |
| 819 | if (!FileSysUtilsPathExists(library_path)) { |
| 820 | error_ss << filename << " library " << library_path << " does not appear to exist" ; |
| 821 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 822 | return; |
| 823 | } |
| 824 | } else { |
| 825 | // Otherwise, treat the library path as a relative path based on the JSON file. |
| 826 | std::string combined_path; |
| 827 | if (!locate_library(filename, library_path, combined_path)) { |
| 828 | error_ss << filename << " library " << combined_path << " does not appear to exist" ; |
| 829 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 830 | return; |
| 831 | } |
| 832 | library_path = combined_path; |
| 833 | } |
| 834 | } |
| 835 | |
| 836 | std::string description; |
| 837 | if (!layer_root_node["description" ].isNull() && layer_root_node["description" ].isString()) { |
| 838 | description = layer_root_node["description" ].asString(); |
| 839 | } |
| 840 | |
| 841 | // Add this layer manifest file |
| 842 | manifest_files.emplace_back( |
| 843 | new ApiLayerManifestFile(type, filename, layer_name, description, api_version, implementation_version, library_path)); |
| 844 | |
| 845 | // Add any extensions to it after the fact. |
| 846 | manifest_files.back()->ParseCommon(layer_root_node); |
| 847 | } |
| 848 | |
| 849 | void ApiLayerManifestFile::CreateIfValid(ManifestFileType type, const std::string &filename, |
| 850 | std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) { |
| 851 | std::ifstream json_stream(filename, std::ifstream::in); |
| 852 | if (!json_stream.is_open()) { |
| 853 | std::ostringstream error_ss("ApiLayerManifestFile::CreateIfValid " ); |
| 854 | error_ss << "failed to open " << filename << ". Does it exist?" ; |
| 855 | LoaderLogger::LogErrorMessage("" , error_ss.str()); |
| 856 | return; |
| 857 | } |
| 858 | CreateIfValid(type, filename, json_stream, &ApiLayerManifestFile::LocateLibraryRelativeToJson, manifest_files); |
| 859 | } |
| 860 | |
| 861 | bool ApiLayerManifestFile::LocateLibraryRelativeToJson( |
| 862 | const std::string &json_filename, const std::string &library_path, |
| 863 | std::string &out_combined_path) { // Otherwise, treat the library path as a relative path based on the JSON file. |
| 864 | std::string combined_path; |
| 865 | std::string file_parent; |
| 866 | if (!FileSysUtilsGetParentPath(json_filename, file_parent) || |
| 867 | !FileSysUtilsCombinePaths(file_parent, library_path, combined_path) || !FileSysUtilsPathExists(combined_path)) { |
| 868 | out_combined_path = combined_path; |
| 869 | return false; |
| 870 | } |
| 871 | out_combined_path = combined_path; |
| 872 | return true; |
| 873 | } |
| 874 | |
| 875 | #ifdef XR_USE_PLATFORM_ANDROID |
| 876 | bool ApiLayerManifestFile::LocateLibraryInAssets(const std::string & /* json_filename */, const std::string &library_path, |
| 877 | std::string &out_combined_path) { |
| 878 | std::string combined_path; |
| 879 | std::string file_parent = GetAndroidNativeLibraryDir(); |
| 880 | if (!FileSysUtilsCombinePaths(file_parent, library_path, combined_path) || !FileSysUtilsPathExists(combined_path)) { |
| 881 | out_combined_path = combined_path; |
| 882 | return false; |
| 883 | } |
| 884 | out_combined_path = combined_path; |
| 885 | return true; |
| 886 | } |
| 887 | #endif |
| 888 | |
| 889 | void ApiLayerManifestFile::PopulateApiLayerProperties(XrApiLayerProperties &props) const { |
| 890 | props.layerVersion = _implementation_version; |
| 891 | props.specVersion = XR_MAKE_VERSION(_api_version.major, _api_version.minor, _api_version.patch); |
| 892 | strncpy(props.layerName, _layer_name.c_str(), XR_MAX_API_LAYER_NAME_SIZE - 1); |
| 893 | if (_layer_name.size() >= XR_MAX_API_LAYER_NAME_SIZE - 1) { |
| 894 | props.layerName[XR_MAX_API_LAYER_NAME_SIZE - 1] = '\0'; |
| 895 | } |
| 896 | strncpy(props.description, _description.c_str(), XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1); |
| 897 | if (_description.size() >= XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1) { |
| 898 | props.description[XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1] = '\0'; |
| 899 | } |
| 900 | } |
| 901 | |
| 902 | // Find all layer manifest files in the appropriate search paths/registries for the given type. |
| 903 | XrResult ApiLayerManifestFile::FindManifestFiles(ManifestFileType type, |
| 904 | std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) { |
| 905 | std::string relative_path; |
| 906 | std::string override_env_var; |
| 907 | std::string registry_location; |
| 908 | |
| 909 | // Add the appropriate top-level folders for the relative path. These should be |
| 910 | // the string "openxr/" followed by the API major version as a string. |
| 911 | relative_path = OPENXR_RELATIVE_PATH; |
| 912 | relative_path += std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)); |
| 913 | |
| 914 | switch (type) { |
| 915 | case MANIFEST_TYPE_IMPLICIT_API_LAYER: |
| 916 | relative_path += OPENXR_IMPLICIT_API_LAYER_RELATIVE_PATH; |
| 917 | override_env_var = "" ; |
| 918 | #ifdef XR_OS_WINDOWS |
| 919 | registry_location = OPENXR_IMPLICIT_API_LAYER_REGISTRY_LOCATION; |
| 920 | #endif |
| 921 | break; |
| 922 | case MANIFEST_TYPE_EXPLICIT_API_LAYER: |
| 923 | relative_path += OPENXR_EXPLICIT_API_LAYER_RELATIVE_PATH; |
| 924 | override_env_var = OPENXR_API_LAYER_PATH_ENV_VAR; |
| 925 | #ifdef XR_OS_WINDOWS |
| 926 | registry_location = OPENXR_EXPLICIT_API_LAYER_REGISTRY_LOCATION; |
| 927 | #endif |
| 928 | break; |
| 929 | default: |
| 930 | LoaderLogger::LogErrorMessage("" , "ApiLayerManifestFile::FindManifestFiles - unknown manifest file requested" ); |
| 931 | return XR_ERROR_FILE_ACCESS_ERROR; |
| 932 | } |
| 933 | |
| 934 | bool override_active = false; |
| 935 | std::vector<std::string> filenames; |
| 936 | ReadDataFilesInSearchPaths(override_env_var, relative_path, override_active, filenames); |
| 937 | |
| 938 | #ifdef XR_OS_WINDOWS |
| 939 | // Read the registry if the override wasn't active. |
| 940 | if (!override_active) { |
| 941 | ReadLayerDataFilesInRegistry(registry_location, filenames); |
| 942 | } |
| 943 | #endif |
| 944 | |
| 945 | for (std::string &cur_file : filenames) { |
| 946 | ApiLayerManifestFile::CreateIfValid(type, cur_file, manifest_files); |
| 947 | } |
| 948 | |
| 949 | #ifdef XR_USE_PLATFORM_ANDROID |
| 950 | ApiLayerManifestFile::AddManifestFilesAndroid(type, manifest_files); |
| 951 | #endif // XR_USE_PLATFORM_ANDROID |
| 952 | |
| 953 | return XR_SUCCESS; |
| 954 | } |
| 955 | |