Path: blob/master/thirdparty/openxr/src/loader/manifest_file.cpp
20779 views
// Copyright (c) 2017-2025 The Khronos Group Inc.1// Copyright (c) 2017-2019 Valve Corporation2// Copyright (c) 2017-2019 LunarG, Inc.3//4// SPDX-License-Identifier: Apache-2.0 OR MIT5//6// Initial Authors: Mark Young <[email protected]>, Dave Houlton <[email protected]>7//89#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)10#define _CRT_SECURE_NO_WARNINGS11#endif // defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)1213#include "manifest_file.hpp"1415#ifdef OPENXR_HAVE_COMMON_CONFIG16#include "common_config.h"17#endif // OPENXR_HAVE_COMMON_CONFIG1819#include "filesystem_utils.hpp"20#include "loader_init_data.hpp"21#include "loader_platform.hpp"22#include "loader_properties.hpp"23#include "platform_utils.hpp"24#include "loader_logger.hpp"25#include "unique_asset.h"2627#include <json/json.h>28#include <openxr/openxr.h>2930#include <algorithm>31#include <cstdlib>32#include <cstdio>33#include <cstring>34#include <fstream>35#include <memory>36#include <sstream>37#include <stdexcept>38#include <string>39#include <unordered_map>40#include <utility>41#include <vector>4243#ifndef FALLBACK_CONFIG_DIRS44#define FALLBACK_CONFIG_DIRS "/etc/xdg"45#endif // !FALLBACK_CONFIG_DIRS4647#ifndef FALLBACK_DATA_DIRS48#define FALLBACK_DATA_DIRS "/usr/local/share:/usr/share"49#endif // !FALLBACK_DATA_DIRS5051#ifndef SYSCONFDIR52#define SYSCONFDIR "/etc"53#endif // !SYSCONFDIR5455#if defined(XR_USE_PLATFORM_ANDROID) && defined(XR_HAS_REQUIRED_PLATFORM_LOADER_INIT_STRUCT)56#include <android/asset_manager.h>57#endif5859#ifdef XRLOADER_DISABLE_EXCEPTION_HANDLING60#if JSON_USE_EXCEPTIONS61#error \62"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."63#endif // JSON_USE_EXCEPTIONS64#endif // !XRLOADER_DISABLE_EXCEPTION_HANDLING6566#include "runtime_interface.hpp"6768// Utility functions for finding files in the appropriate paths6970static inline bool StringEndsWith(const std::string &value, const std::string &ending) {71if (ending.size() > value.size()) {72return false;73}74return std::equal(ending.rbegin(), ending.rend(), value.rbegin());75}7677// If the file found is a manifest file name, add it to the out_files manifest list.78static bool AddIfJson(const std::string &full_file, std::vector<std::string> &manifest_files) {79if (full_file.empty() || !StringEndsWith(full_file, ".json")) {80return false;81}82manifest_files.push_back(full_file);83return true;84}8586// Check the current path for any manifest files. If the provided search_path is a directory, look for87// all included JSON files in that directory. Otherwise, just check the provided search_path which should88// be a single filename.89static void CheckAllFilesInThePath(const std::string &search_path, bool is_directory_list,90std::vector<std::string> &manifest_files) {91if (FileSysUtilsPathExists(search_path)) {92std::string absolute_path;93if (!is_directory_list) {94// If the file exists, try to add it95if (FileSysUtilsIsRegularFile(search_path)) {96FileSysUtilsGetAbsolutePath(search_path, absolute_path);97AddIfJson(absolute_path, manifest_files);98}99} else {100std::vector<std::string> files;101if (FileSysUtilsFindFilesInPath(search_path, files)) {102for (std::string &cur_file : files) {103std::string relative_path;104FileSysUtilsCombinePaths(search_path, cur_file, relative_path);105if (!FileSysUtilsGetAbsolutePath(relative_path, absolute_path)) {106continue;107}108AddIfJson(absolute_path, manifest_files);109}110}111}112}113}114115// Add all manifest files in the provided paths to the manifest_files list. If search_path116// is made up of directory listings (versus direct manifest file names) search each path for117// any manifest files.118static void AddFilesInPath(const std::string &search_path, bool is_directory_list, std::vector<std::string> &manifest_files) {119std::size_t last_found = 0;120std::size_t found = search_path.find_first_of(PATH_SEPARATOR);121std::string cur_search;122123// Handle any path listings in the string (separated by the appropriate path separator)124while (found != std::string::npos) {125// substr takes a start index and length.126std::size_t length = found - last_found;127cur_search = search_path.substr(last_found, length);128129CheckAllFilesInThePath(cur_search, is_directory_list, manifest_files);130131// This works around issue if multiple path separator follow each other directly.132last_found = found;133while (found == last_found) {134last_found = found + 1;135found = search_path.find_first_of(PATH_SEPARATOR, last_found);136}137}138139// If there's something remaining in the string, copy it over140if (last_found < search_path.size()) {141cur_search = search_path.substr(last_found);142CheckAllFilesInThePath(cur_search, is_directory_list, manifest_files);143}144}145146// Copy all paths listed in the cur_path string into output_path and append the appropriate relative_path onto the end of each.147static void CopyIncludedPaths(bool is_directory_list, const std::string &cur_path, const std::string &relative_path,148std::string &output_path) {149if (!cur_path.empty()) {150std::size_t last_found = 0;151std::size_t found = cur_path.find_first_of(PATH_SEPARATOR);152153// Handle any path listings in the string (separated by the appropriate path separator)154while (found != std::string::npos) {155std::size_t length = found - last_found;156output_path += cur_path.substr(last_found, length);157if (is_directory_list && (cur_path[found - 1] != '\\' && cur_path[found - 1] != '/')) {158output_path += DIRECTORY_SYMBOL;159}160output_path += relative_path;161output_path += PATH_SEPARATOR;162163last_found = found;164found = cur_path.find_first_of(PATH_SEPARATOR, found + 1);165}166167// If there's something remaining in the string, copy it over168size_t last_char = cur_path.size() - 1;169if (last_found != last_char) {170output_path += cur_path.substr(last_found);171if (is_directory_list && (cur_path[last_char] != '\\' && cur_path[last_char] != '/')) {172output_path += DIRECTORY_SYMBOL;173}174output_path += relative_path;175output_path += PATH_SEPARATOR;176}177}178}179180// Look for data files in the provided paths, but first check the environment override to determine if we should use that instead.181static void ReadDataFilesInSearchPaths(const std::string &override_env_var, const std::string &relative_path, bool &override_active,182std::vector<std::string> &manifest_files) {183std::string override_path;184std::string search_path;185186if (!override_env_var.empty()) {187bool permit_override = true;188#ifndef XR_OS_WINDOWS189if (geteuid() != getuid() || getegid() != getgid()) {190// Don't allow setuid apps to use the env var191permit_override = false;192}193#endif194if (permit_override) {195override_path = LoaderProperty::GetSecure(override_env_var);196}197}198199if (!override_path.empty()) {200CopyIncludedPaths(true, override_path, "", search_path);201override_active = true;202} else {203override_active = false;204#if !defined(XR_OS_WINDOWS) && !defined(XR_OS_ANDROID)205const char home_additional[] = ".local/share/";206207// Determine how much space is needed to generate the full search path208// for the current manifest files.209std::string xdg_conf_dirs = LoaderProperty::GetSecure("XDG_CONFIG_DIRS");210std::string xdg_data_dirs = LoaderProperty::GetSecure("XDG_DATA_DIRS");211std::string xdg_data_home = LoaderProperty::GetSecure("XDG_DATA_HOME");212std::string home = LoaderProperty::GetSecure("HOME");213214if (xdg_conf_dirs.empty()) {215CopyIncludedPaths(true, FALLBACK_CONFIG_DIRS, relative_path, search_path);216} else {217CopyIncludedPaths(true, xdg_conf_dirs, relative_path, search_path);218}219220CopyIncludedPaths(true, SYSCONFDIR, relative_path, search_path);221#if defined(EXTRASYSCONFDIR)222CopyIncludedPaths(true, EXTRASYSCONFDIR, relative_path, search_path);223#endif224225if (xdg_data_dirs.empty()) {226CopyIncludedPaths(true, FALLBACK_DATA_DIRS, relative_path, search_path);227} else {228CopyIncludedPaths(true, xdg_data_dirs, relative_path, search_path);229}230231if (!xdg_data_home.empty()) {232CopyIncludedPaths(true, xdg_data_home, relative_path, search_path);233} else if (!home.empty()) {234std::string relative_home_path = home_additional;235relative_home_path += relative_path;236CopyIncludedPaths(true, home, relative_home_path, search_path);237}238#elif defined(XR_OS_ANDROID)239CopyIncludedPaths(true, "/product/etc", relative_path, search_path);240CopyIncludedPaths(true, "/odm/etc", relative_path, search_path);241CopyIncludedPaths(true, "/oem/etc", relative_path, search_path);242CopyIncludedPaths(true, "/vendor/etc", relative_path, search_path);243CopyIncludedPaths(true, "/system/etc", relative_path, search_path);244#else245(void)relative_path;246#endif247}248249// Now, parse the paths and add any manifest files found in them.250AddFilesInPath(search_path, true, manifest_files);251}252253#ifdef XR_OS_LINUX254255// Get an XDG environment variable with a $HOME-relative default256static std::string GetXDGEnvHome(const char *name, const char *fallback_path) {257std::string result = LoaderProperty::GetSecure(name);258if (!result.empty()) {259return result;260}261result = LoaderProperty::GetSecure("HOME");262if (result.empty()) {263return result;264}265result += "/";266result += fallback_path;267return result;268}269270// Get an XDG environment variable with absolute defaults271static std::string GetXDGEnvAbsolute(const char *name, const char *fallback_paths) {272std::string result = LoaderProperty::GetSecure(name);273if (!result.empty()) {274return result;275}276return fallback_paths;277}278279/// @param rt_dir_prefix Directory prefix with a trailing slash280static bool FindEitherActiveRuntimeFilename(const char *prefix_desc, const std::string &rt_dir_prefix, uint16_t major_version,281std::string &out) {282{283std::ostringstream oss;284oss << "Looking for active_runtime." XR_ARCH_ABI ".json or active_runtime.json in ";285oss << prefix_desc;286oss << ": ";287oss << rt_dir_prefix;288289LoaderLogger::LogInfoMessage("", oss.str());290}291{292auto decorated_path = rt_dir_prefix + std::to_string(major_version) + "/active_runtime." XR_ARCH_ABI ".json";293294if (FileSysUtilsPathExists(decorated_path)) {295out = decorated_path;296return true;297}298}299{300auto undecorated_path = rt_dir_prefix + std::to_string(major_version) + "/active_runtime.json";301302if (FileSysUtilsPathExists(undecorated_path)) {303out = undecorated_path;304return true;305}306}307return false;308}309// Return the first instance of relative_path occurring in an XDG config dir according to standard310// precedence order.311static bool FindXDGConfigFile(const char *relative_dir, uint16_t major_version, std::string &out) {312const std::string message{"Looking for active_runtime." XR_ARCH_ABI ".json or active_runtime.json"};313LoaderLogger::LogInfoMessage("", message);314std::string dir_prefix = GetXDGEnvHome("XDG_CONFIG_HOME", ".config");315if (!dir_prefix.empty()) {316dir_prefix += "/";317dir_prefix += relative_dir;318if (FindEitherActiveRuntimeFilename("XDG_CONFIG_HOME", dir_prefix, major_version, out)) {319return true;320}321}322323std::istringstream iss(GetXDGEnvAbsolute("XDG_CONFIG_DIRS", FALLBACK_CONFIG_DIRS));324std::string path;325while (std::getline(iss, path, PATH_SEPARATOR)) {326if (path.empty()) {327continue;328}329dir_prefix = std::move(path);330dir_prefix += "/";331dir_prefix += relative_dir;332if (FindEitherActiveRuntimeFilename("an entry of XDG_CONFIG_DIRS", dir_prefix, major_version, out)) {333return true;334}335}336337dir_prefix = SYSCONFDIR;338dir_prefix += "/";339dir_prefix += relative_dir;340if (FindEitherActiveRuntimeFilename("compiled-in SYSCONFDIR", dir_prefix, major_version, out)) {341return true;342}343344#if defined(EXTRASYSCONFDIR)345dir_prefix = EXTRASYSCONFDIR;346dir_prefix += "/";347dir_prefix += relative_dir;348if (FindEitherActiveRuntimeFilename("compiled-in EXTRASYSCONFDIR", dir_prefix, major_version, out)) {349return true;350}351#endif352353out.clear();354return false;355}356357#endif358359#ifdef XR_OS_WINDOWS360361// Look for runtime data files in the provided paths, but first check the environment override to determine362// if we should use that instead.363static void ReadRuntimeDataFilesInRegistry(const std::string &runtime_registry_location,364const std::string &default_runtime_value_name,365std::vector<std::string> &manifest_files) {366HKEY hkey;367DWORD access_flags;368wchar_t value_w[1024];369DWORD value_size_w = sizeof(value_w); // byte size of the buffer.370371// Generate the full registry location for the registry information372std::string full_registry_location = OPENXR_REGISTRY_LOCATION;373full_registry_location += std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION));374full_registry_location += runtime_registry_location;375376const std::wstring full_registry_location_w = utf8_to_wide(full_registry_location);377const std::wstring default_runtime_value_name_w = utf8_to_wide(default_runtime_value_name);378379// Use 64 bit regkey for 64bit application, and use 32 bit regkey in WOW for 32bit application.380access_flags = KEY_QUERY_VALUE;381LONG open_value = RegOpenKeyExW(HKEY_LOCAL_MACHINE, full_registry_location_w.c_str(), 0, access_flags, &hkey);382383if (ERROR_SUCCESS != open_value) {384LoaderLogger::LogWarningMessage("",385"ReadRuntimeDataFilesInRegistry - failed to open registry key " + full_registry_location);386return;387}388389if (ERROR_SUCCESS != RegGetValueW(hkey, nullptr, default_runtime_value_name_w.c_str(),390RRF_RT_REG_SZ | REG_EXPAND_SZ | RRF_ZEROONFAILURE, NULL, reinterpret_cast<LPBYTE>(&value_w),391&value_size_w)) {392LoaderLogger::LogWarningMessage(393"", "ReadRuntimeDataFilesInRegistry - failed to read registry value " + default_runtime_value_name);394} else {395// Not using AddFilesInPath here (as only api_layer manifest paths allow multiple396// separated paths)397// Small time-of-check vs time-of-use issue here but it mainly only affects the error message.398// It does not introduce a security defect.399std::string activeRuntimePath = wide_to_utf8(value_w);400if (FileSysUtilsIsRegularFile(activeRuntimePath)) {401// If the file exists, try to add it402std::string absolute_path;403FileSysUtilsGetAbsolutePath(activeRuntimePath, absolute_path);404if (!AddIfJson(absolute_path, manifest_files)) {405LoaderLogger::LogErrorMessage(406"", "ReadRuntimeDataFilesInRegistry - registry runtime path is not json " + activeRuntimePath);407}408} else {409LoaderLogger::LogErrorMessage(410"", "ReadRuntimeDataFilesInRegistry - registry runtime path does not exist " + activeRuntimePath);411}412}413414RegCloseKey(hkey);415}416417// Look for layer data files in the provided paths, but first check the environment override to determine418// if we should use that instead.419static void ReadLayerDataFilesInRegistry(const std::string ®istry_location, std::vector<std::string> &manifest_files) {420const std::wstring full_registry_location_w =421utf8_to_wide(OPENXR_REGISTRY_LOCATION + std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) + registry_location);422423auto ReadLayerDataFilesInHive = [&](HKEY hive) {424HKEY hkey;425LONG open_value = RegOpenKeyExW(hive, full_registry_location_w.c_str(), 0, KEY_QUERY_VALUE, &hkey);426if (ERROR_SUCCESS != open_value) {427return false;428}429430wchar_t name_w[1024]{};431LONG rtn_value;432DWORD name_size = 1023;433DWORD value;434DWORD value_size = sizeof(value);435DWORD key_index = 0;436while (ERROR_SUCCESS ==437(rtn_value = RegEnumValueW(hkey, key_index++, name_w, &name_size, NULL, NULL, (LPBYTE)&value, &value_size))) {438if (value_size == sizeof(value) && value == 0) {439const std::string filename = wide_to_utf8(name_w);440AddFilesInPath(filename, false, manifest_files);441}442// Reset some items for the next loop443name_size = 1023;444}445446RegCloseKey(hkey);447448return true;449};450451// Do not allow high integrity processes to act on data that can be controlled by medium integrity processes.452const bool readFromCurrentUser = !IsHighIntegrityLevel();453454bool found = ReadLayerDataFilesInHive(HKEY_LOCAL_MACHINE);455if (readFromCurrentUser) {456found |= ReadLayerDataFilesInHive(HKEY_CURRENT_USER);457}458459if (!found) {460std::string warning_message = "ReadLayerDataFilesInRegistry - failed to read registry location ";461warning_message += registry_location;462warning_message += (readFromCurrentUser ? " in either HKEY_LOCAL_MACHINE or HKEY_CURRENT_USER" : " in HKEY_LOCAL_MACHINE");463LoaderLogger::LogWarningMessage("", warning_message);464}465}466467#endif // XR_OS_WINDOWS468469ManifestFile::ManifestFile(ManifestFileType type, const std::string &filename, const std::string &library_path)470: _filename(filename), _type(type), _library_path(library_path) {}471472bool ManifestFile::IsValidJson(const Json::Value &root_node, JsonVersion &version) {473if (root_node["file_format_version"].isNull() || !root_node["file_format_version"].isString()) {474LoaderLogger::LogErrorMessage("", "ManifestFile::IsValidJson - JSON file missing \"file_format_version\"");475return false;476}477std::string file_format = root_node["file_format_version"].asString();478const int num_fields = sscanf(file_format.c_str(), "%u.%u.%u", &version.major, &version.minor, &version.patch);479480// Only version 1.0.0 is defined currently. Eventually we may have more version, but481// some of the versions may only be valid for layers or runtimes specifically.482if (num_fields != 3 || version.major != 1 || version.minor != 0 || version.patch != 0) {483std::ostringstream error_ss;484error_ss << "ManifestFile::IsValidJson - JSON \"file_format_version\" " << version.major << "." << version.minor << "."485<< version.patch << " is not supported";486LoaderLogger::LogErrorMessage("", error_ss.str());487return false;488}489490return true;491}492493static void GetExtensionProperties(const std::vector<ExtensionListing> &extensions, std::vector<XrExtensionProperties> &props) {494for (const auto &ext : extensions) {495auto it =496std::find_if(props.begin(), props.end(), [&](XrExtensionProperties &prop) { return prop.extensionName == ext.name; });497if (it != props.end()) {498it->extensionVersion = std::max(it->extensionVersion, ext.extension_version);499} else {500XrExtensionProperties prop{};501prop.type = XR_TYPE_EXTENSION_PROPERTIES;502strncpy(prop.extensionName, ext.name.c_str(), XR_MAX_EXTENSION_NAME_SIZE - 1);503prop.extensionName[XR_MAX_EXTENSION_NAME_SIZE - 1] = '\0';504prop.extensionVersion = ext.extension_version;505props.push_back(prop);506}507}508}509510// Return any instance extensions found in the manifest files in the proper form for511// OpenXR (XrExtensionProperties).512void ManifestFile::GetInstanceExtensionProperties(std::vector<XrExtensionProperties> &props) {513GetExtensionProperties(_instance_extensions, props);514}515516std::string ManifestFile::GetFunctionName(const std::string &func_name) const {517if (!_functions_renamed.empty()) {518auto found = _functions_renamed.find(func_name);519if (found != _functions_renamed.end()) {520return found->second;521}522}523return func_name;524}525526RuntimeManifestFile::RuntimeManifestFile(const std::string &filename, const std::string &library_path)527: ManifestFile(MANIFEST_TYPE_RUNTIME, filename, library_path) {}528529static void ParseExtension(Json::Value const &ext, std::vector<ExtensionListing> &extensions) {530Json::Value ext_name = ext["name"];531Json::Value ext_version = ext["extension_version"];532533// Allow "extension_version" as a String or a UInt to maintain backwards compatibility, even though it should be a String.534// Internal Issue 1411: https://gitlab.khronos.org/openxr/openxr/-/issues/1411535// Internal MR !1867: https://gitlab.khronos.org/openxr/openxr/-/merge_requests/1867536if (ext_name.isString() && (ext_version.isString() || ext_version.isUInt())) {537ExtensionListing ext_listing = {};538ext_listing.name = ext_name.asString();539if (ext_version.isUInt()) {540ext_listing.extension_version = ext_version.asUInt();541} else {542ext_listing.extension_version = atoi(ext_version.asString().c_str());543}544extensions.push_back(ext_listing);545}546}547548void ManifestFile::ParseCommon(Json::Value const &root_node) {549const Json::Value &inst_exts = root_node["instance_extensions"];550if (!inst_exts.isNull() && inst_exts.isArray()) {551for (const auto &ext : inst_exts) {552ParseExtension(ext, _instance_extensions);553}554}555const Json::Value &funcs_renamed = root_node["functions"];556if (!funcs_renamed.isNull() && !funcs_renamed.empty()) {557for (Json::ValueConstIterator func_it = funcs_renamed.begin(); func_it != funcs_renamed.end(); ++func_it) {558if (!(*func_it).isString()) {559LoaderLogger::LogWarningMessage(560"", "ManifestFile::ParseCommon " + _filename + " \"functions\" section contains non-string values.");561continue;562}563std::string original_name = func_it.key().asString();564std::string new_name = (*func_it).asString();565_functions_renamed.emplace(original_name, new_name);566}567}568}569570void RuntimeManifestFile::CreateIfValid(std::string const &filename,571std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) {572std::ifstream json_stream(filename, std::ifstream::in);573574LoaderLogger::LogInfoMessage("", "RuntimeManifestFile::CreateIfValid - attempting to load " + filename);575std::ostringstream error_ss("RuntimeManifestFile::CreateIfValid ");576if (!json_stream.is_open()) {577error_ss << "failed to open " << filename << ". Does it exist?";578LoaderLogger::LogErrorMessage("", error_ss.str());579return;580}581Json::CharReaderBuilder builder;582std::string errors;583Json::Value root_node = Json::nullValue;584if (!Json::parseFromStream(builder, json_stream, &root_node, &errors) || !root_node.isObject()) {585error_ss << "failed to parse " << filename << ".";586if (!errors.empty()) {587error_ss << " (Error message: " << errors << ")";588}589error_ss << " Is it a valid runtime manifest file?";590LoaderLogger::LogErrorMessage("", error_ss.str());591return;592}593594CreateIfValid(root_node, filename, manifest_files);595}596597void RuntimeManifestFile::CreateIfValid(const Json::Value &root_node, const std::string &filename,598std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) {599std::ostringstream error_ss("RuntimeManifestFile::CreateIfValid ");600JsonVersion file_version = {};601if (!ManifestFile::IsValidJson(root_node, file_version)) {602error_ss << "isValidJson indicates " << filename << " is not a valid manifest file.";603LoaderLogger::LogErrorMessage("", error_ss.str());604return;605}606const Json::Value &runtime_root_node = root_node["runtime"];607// The Runtime manifest file needs the "runtime" root as well as a sub-node for "library_path". If any of those aren't there,608// fail.609if (runtime_root_node.isNull() || runtime_root_node["library_path"].isNull() || !runtime_root_node["library_path"].isString()) {610error_ss << filename << " is missing required fields. Verify all proper fields exist.";611LoaderLogger::LogErrorMessage("", error_ss.str());612return;613}614615std::string lib_path = runtime_root_node["library_path"].asString();616617// If the library_path variable has no directory symbol, it's just a file name and should be accessible on the618// global library path.619if (lib_path.find('\\') != std::string::npos || lib_path.find('/') != std::string::npos) {620// If the library_path is an absolute path, just use that as-is.621if (!FileSysUtilsIsAbsolutePath(lib_path)) {622// Otherwise, treat the library path as a relative path based on the JSON file.623std::string canonical_path;624std::string combined_path;625std::string file_parent;626// Search relative to the real manifest file, not relative to the symlink627if (!FileSysUtilsGetCanonicalPath(filename, canonical_path)) {628// Give relative to the non-canonical path a chance629canonical_path = filename;630}631if (!FileSysUtilsGetParentPath(canonical_path, file_parent) ||632!FileSysUtilsCombinePaths(file_parent, lib_path, combined_path)) {633error_ss << filename << " filesystem operations failed for path " << canonical_path;634LoaderLogger::LogErrorMessage("", error_ss.str());635return;636}637lib_path = combined_path;638}639}640641// Add this runtime manifest file642manifest_files.emplace_back(new RuntimeManifestFile(filename, lib_path));643644// Add any extensions to it after the fact.645// Handle any renamed functions646manifest_files.back()->ParseCommon(runtime_root_node);647}648649// Find all manifest files in the appropriate search paths/registries for the given type.650XrResult RuntimeManifestFile::FindManifestFiles(const std::string &openxr_command,651std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) {652XrResult result = XR_SUCCESS;653std::string filename = LoaderProperty::GetSecure(OPENXR_RUNTIME_JSON_ENV_VAR);654if (!filename.empty()) {655LoaderLogger::LogInfoMessage(656openxr_command,657"RuntimeManifestFile::FindManifestFiles - using environment variable override runtime file " + filename);658} else {659#ifdef XR_OS_WINDOWS660std::vector<std::string> filenames;661ReadRuntimeDataFilesInRegistry("", "ActiveRuntime", filenames);662if (filenames.size() == 0) {663LoaderLogger::LogErrorMessage(664openxr_command, "RuntimeManifestFile::FindManifestFiles - failed to find active runtime file in registry");665return XR_ERROR_RUNTIME_UNAVAILABLE;666}667if (filenames.size() > 1) {668LoaderLogger::LogWarningMessage(669openxr_command, "RuntimeManifestFile::FindManifestFiles - found too many default runtime files in registry");670}671filename = filenames[0];672LoaderLogger::LogInfoMessage(openxr_command,673"RuntimeManifestFile::FindManifestFiles - using registry-specified runtime file " + filename);674#elif defined(XR_OS_LINUX)675676if (!FindXDGConfigFile("openxr/", XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), filename)) {677LoaderLogger::LogErrorMessage(678openxr_command,679"RuntimeManifestFile::FindManifestFiles - failed to determine active runtime file path for this environment");680return XR_ERROR_RUNTIME_UNAVAILABLE;681}682#else // !defined(XR_OS_WINDOWS) && !defined(XR_OS_LINUX)683684#if defined(XR_USE_PLATFORM_ANDROID) && defined(XR_HAS_REQUIRED_PLATFORM_LOADER_INIT_STRUCT)685Json::Value virtualManifest;686result = GetPlatformRuntimeVirtualManifest(virtualManifest);687if (XR_SUCCESS == result) {688RuntimeManifestFile::CreateIfValid(virtualManifest, "", manifest_files);689return result;690}691#endif // defined(XR_USE_PLATFORM_ANDROID) && defined(XR_HAS_REQUIRED_PLATFORM_LOADER_INIT_STRUCT)692693if (!PlatformGetGlobalRuntimeFileName(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), filename)) {694LoaderLogger::LogErrorMessage(695openxr_command,696"RuntimeManifestFile::FindManifestFiles - failed to determine active runtime file path for this environment");697return XR_ERROR_RUNTIME_UNAVAILABLE;698}699result = XR_SUCCESS;700LoaderLogger::LogInfoMessage(openxr_command,701"RuntimeManifestFile::FindManifestFiles - using global runtime file " + filename);702#endif // !defined(XR_OS_WINDOWS) && !defined(XR_OS_LINUX)703}704RuntimeManifestFile::CreateIfValid(filename, manifest_files);705706return result;707}708709ApiLayerManifestFile::ApiLayerManifestFile(ManifestFileType type, const std::string &filename, const std::string &layer_name,710const std::string &description, const JsonVersion &api_version,711const uint32_t &implementation_version, const std::string &library_path)712: ManifestFile(type, filename, library_path),713_api_version(api_version),714_layer_name(layer_name),715_description(description),716_implementation_version(implementation_version) {}717718#if defined(XR_USE_PLATFORM_ANDROID) && defined(XR_HAS_REQUIRED_PLATFORM_LOADER_INIT_STRUCT)719void ApiLayerManifestFile::AddManifestFilesAndroid(const std::string &openxr_command, ManifestFileType type,720std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {721if (!LoaderInitData::instance().initialized()) {722// This will happen for applications that do not call xrInitializeLoaderKHR723LoaderLogger::LogWarningMessage(724openxr_command,725"ApiLayerManifestFile::AddManifestFilesAndroid unable to add manifest files LoaderInitData not initialized.");726return;727}728729AAssetManager *assetManager = (AAssetManager *)GetAndroidAssetManager();730std::vector<std::string> filenames;731{732std::string search_path = "";733switch (type) {734case MANIFEST_TYPE_IMPLICIT_API_LAYER:735search_path = "openxr/1/api_layers/implicit.d/";736break;737case MANIFEST_TYPE_EXPLICIT_API_LAYER:738search_path = "openxr/1/api_layers/explicit.d/";739break;740default:741return;742}743744UniqueAssetDir dir{AAssetManager_openDir(assetManager, search_path.c_str())};745if (!dir) {746return;747}748const std::string json = ".json";749const char *fn = nullptr;750while ((fn = AAssetDir_getNextFileName(dir.get())) != nullptr) {751const std::string filename = search_path + fn;752if (filename.size() < json.size()) {753continue;754}755if (filename.compare(filename.size() - json.size(), json.size(), json) == 0) {756filenames.push_back(filename);757}758}759}760for (const auto &filename : filenames) {761UniqueAsset asset{AAssetManager_open(assetManager, filename.c_str(), AASSET_MODE_BUFFER)};762if (!asset) {763LoaderLogger::LogWarningMessage(764openxr_command, "ApiLayerManifestFile::AddManifestFilesAndroid unable to open asset " + filename + ", skipping");765766continue;767}768size_t length = AAsset_getLength(asset.get());769const char *buf = reinterpret_cast<const char *>(AAsset_getBuffer(asset.get()));770if (!buf) {771LoaderLogger::LogWarningMessage(772openxr_command, "ApiLayerManifestFile::AddManifestFilesAndroid unable to access asset" + filename + ", skipping");773774continue;775}776std::istringstream json_stream(std::string{buf, length});777778CreateIfValid(type, filename, json_stream, &ApiLayerManifestFile::LocateLibraryInAssets, manifest_files);779}780}781#endif // defined(XR_USE_PLATFORM_ANDROID) && defined(XR_HAS_REQUIRED_PLATFORM_LOADER_INIT_STRUCT)782783void ApiLayerManifestFile::CreateIfValid(ManifestFileType type, const std::string &filename, std::istream &json_stream,784LibraryLocator locate_library,785std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {786std::ostringstream error_ss("ApiLayerManifestFile::CreateIfValid ");787Json::CharReaderBuilder builder;788std::string errors;789Json::Value root_node = Json::nullValue;790if (!Json::parseFromStream(builder, json_stream, &root_node, &errors) || !root_node.isObject()) {791error_ss << "failed to parse " << filename << ".";792if (!errors.empty()) {793error_ss << " (Error message: " << errors << ")";794}795error_ss << " Is it a valid layer manifest file?";796LoaderLogger::LogErrorMessage("", error_ss.str());797return;798}799JsonVersion file_version = {};800if (!ManifestFile::IsValidJson(root_node, file_version)) {801error_ss << "isValidJson indicates " << filename << " is not a valid manifest file.";802LoaderLogger::LogErrorMessage("", error_ss.str());803return;804}805806Json::Value layer_root_node = root_node["api_layer"];807808// The API Layer manifest file needs the "api_layer" root as well as other sub-nodes.809// If any of those aren't there, fail.810if (layer_root_node.isNull() || layer_root_node["name"].isNull() || !layer_root_node["name"].isString() ||811layer_root_node["api_version"].isNull() || !layer_root_node["api_version"].isString() ||812layer_root_node["library_path"].isNull() || !layer_root_node["library_path"].isString() ||813layer_root_node["implementation_version"].isNull() || !layer_root_node["implementation_version"].isString()) {814error_ss << filename << " is missing required fields. Verify all proper fields exist.";815LoaderLogger::LogErrorMessage("", error_ss.str());816return;817}818if (MANIFEST_TYPE_IMPLICIT_API_LAYER == type) {819bool enabled = true;820// Implicit layers require the disable environment variable.821if (layer_root_node["disable_environment"].isNull() || !layer_root_node["disable_environment"].isString()) {822error_ss << "Implicit layer " << filename << " is missing \"disable_environment\"";823LoaderLogger::LogErrorMessage("", error_ss.str());824return;825}826// Check if there's an enable environment variable provided827if (!layer_root_node["enable_environment"].isNull() && layer_root_node["enable_environment"].isString()) {828std::string env_var = layer_root_node["enable_environment"].asString();829// If it's not set in the environment, disable the layer830if (!LoaderProperty::IsSet(env_var)) {831enabled = false;832}833}834// Check for the disable environment variable, which must be provided in the JSON835std::string env_var = layer_root_node["disable_environment"].asString();836// If the env var is set, disable the layer. Disable env var overrides enable above837if (LoaderProperty::IsSet(env_var)) {838enabled = false;839}840841// Not enabled, so pretend like it isn't even there.842if (!enabled) {843error_ss << "Implicit layer " << filename << " is disabled";844LoaderLogger::LogInfoMessage("", error_ss.str());845return;846}847}848std::string layer_name = layer_root_node["name"].asString();849std::string api_version_string = layer_root_node["api_version"].asString();850JsonVersion api_version = {};851const int num_fields = sscanf(api_version_string.c_str(), "%u.%u", &api_version.major, &api_version.minor);852api_version.patch = 0;853854if ((num_fields != 2) || (api_version.major == 0 && api_version.minor == 0) ||855api_version.major > XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) {856error_ss << "layer " << filename << " has invalid API Version. Skipping layer.";857LoaderLogger::LogWarningMessage("", error_ss.str());858return;859}860861uint32_t implementation_version = atoi(layer_root_node["implementation_version"].asString().c_str());862std::string library_path = layer_root_node["library_path"].asString();863864// If the library_path variable has no directory symbol, it's just a file name and should be accessible on the865// global library path.866if (library_path.find('\\') != std::string::npos || library_path.find('/') != std::string::npos) {867// If the library_path is an absolute path, just use that if it exists868if (FileSysUtilsIsAbsolutePath(library_path)) {869if (!FileSysUtilsPathExists(library_path)) {870error_ss << filename << " library " << library_path << " does not appear to exist";871LoaderLogger::LogErrorMessage("", error_ss.str());872return;873}874} else {875// Otherwise, treat the library path as a relative path based on the JSON file.876std::string combined_path;877if (!locate_library(filename, library_path, combined_path)) {878error_ss << filename << " library " << combined_path << " does not appear to exist";879LoaderLogger::LogErrorMessage("", error_ss.str());880return;881}882library_path = combined_path;883}884}885886std::string description;887if (!layer_root_node["description"].isNull() && layer_root_node["description"].isString()) {888description = layer_root_node["description"].asString();889}890891// Add this layer manifest file892manifest_files.emplace_back(893new ApiLayerManifestFile(type, filename, layer_name, description, api_version, implementation_version, library_path));894895// Add any extensions to it after the fact.896manifest_files.back()->ParseCommon(layer_root_node);897}898899void ApiLayerManifestFile::CreateIfValid(ManifestFileType type, const std::string &filename,900std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {901std::ifstream json_stream(filename, std::ifstream::in);902if (!json_stream.is_open()) {903std::ostringstream error_ss("ApiLayerManifestFile::CreateIfValid ");904error_ss << "failed to open " << filename << ". Does it exist?";905LoaderLogger::LogErrorMessage("", error_ss.str());906return;907}908CreateIfValid(type, filename, json_stream, &ApiLayerManifestFile::LocateLibraryRelativeToJson, manifest_files);909}910911bool ApiLayerManifestFile::LocateLibraryRelativeToJson(912const std::string &json_filename, const std::string &library_path,913std::string &out_combined_path) { // Otherwise, treat the library path as a relative path based on the JSON file.914std::string combined_path;915std::string file_parent;916if (!FileSysUtilsGetParentPath(json_filename, file_parent) ||917!FileSysUtilsCombinePaths(file_parent, library_path, combined_path) || !FileSysUtilsPathExists(combined_path)) {918out_combined_path = combined_path;919return false;920}921out_combined_path = combined_path;922return true;923}924925#if defined(XR_USE_PLATFORM_ANDROID) && defined(XR_HAS_REQUIRED_PLATFORM_LOADER_INIT_STRUCT)926bool ApiLayerManifestFile::LocateLibraryInAssets(const std::string & /* json_filename */, const std::string &library_path,927std::string &out_combined_path) {928std::string combined_path;929std::string file_parent = GetAndroidNativeLibraryDir();930if (!FileSysUtilsCombinePaths(file_parent, library_path, combined_path) || !FileSysUtilsPathExists(combined_path)) {931out_combined_path = combined_path;932return false;933}934out_combined_path = combined_path;935return true;936}937#endif // defined(XR_USE_PLATFORM_ANDROID) && defined(XR_HAS_REQUIRED_PLATFORM_LOADER_INIT_STRUCT)938939void ApiLayerManifestFile::PopulateApiLayerProperties(XrApiLayerProperties &props) const {940props.layerVersion = _implementation_version;941props.specVersion = XR_MAKE_VERSION(_api_version.major, _api_version.minor, _api_version.patch);942strncpy(props.layerName, _layer_name.c_str(), XR_MAX_API_LAYER_NAME_SIZE - 1);943if (_layer_name.size() >= XR_MAX_API_LAYER_NAME_SIZE - 1) {944props.layerName[XR_MAX_API_LAYER_NAME_SIZE - 1] = '\0';945}946strncpy(props.description, _description.c_str(), XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1);947if (_description.size() >= XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1) {948props.description[XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1] = '\0';949}950}951952// Find all layer manifest files in the appropriate search paths/registries for the given type.953XrResult ApiLayerManifestFile::FindManifestFiles(const std::string &openxr_command, ManifestFileType type,954std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {955std::string relative_path;956std::string override_env_var;957#ifdef XR_OS_WINDOWS958std::string registry_location;959#endif960961// Add the appropriate top-level folders for the relative path. These should be962// the string "openxr/" followed by the API major version as a string.963relative_path = OPENXR_RELATIVE_PATH;964relative_path += std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION));965966switch (type) {967case MANIFEST_TYPE_IMPLICIT_API_LAYER:968relative_path += OPENXR_IMPLICIT_API_LAYER_RELATIVE_PATH;969override_env_var = "";970#ifdef XR_OS_WINDOWS971registry_location = OPENXR_IMPLICIT_API_LAYER_REGISTRY_LOCATION;972#endif973break;974case MANIFEST_TYPE_EXPLICIT_API_LAYER:975relative_path += OPENXR_EXPLICIT_API_LAYER_RELATIVE_PATH;976override_env_var = OPENXR_API_LAYER_PATH_ENV_VAR;977#ifdef XR_OS_WINDOWS978registry_location = OPENXR_EXPLICIT_API_LAYER_REGISTRY_LOCATION;979#endif980break;981case MANIFEST_TYPE_UNDEFINED:982case MANIFEST_TYPE_RUNTIME:983default:984LoaderLogger::LogErrorMessage(openxr_command,985"ApiLayerManifestFile::FindManifestFiles - unknown manifest file requested");986return XR_ERROR_FILE_ACCESS_ERROR;987}988989bool override_active = false;990std::vector<std::string> filenames;991ReadDataFilesInSearchPaths(override_env_var, relative_path, override_active, filenames);992993#ifdef XR_OS_WINDOWS994// Read the registry if the override wasn't active.995if (!override_active) {996ReadLayerDataFilesInRegistry(registry_location, filenames);997}998#endif9991000for (std::string &cur_file : filenames) {1001ApiLayerManifestFile::CreateIfValid(type, cur_file, manifest_files);1002}10031004#if defined(XR_USE_PLATFORM_ANDROID) && defined(XR_HAS_REQUIRED_PLATFORM_LOADER_INIT_STRUCT)1005ApiLayerManifestFile::AddManifestFilesAndroid(openxr_command, type, manifest_files);1006#endif // defined(XR_USE_PLATFORM_ANDROID) && defined(XR_HAS_REQUIRED_PLATFORM_LOADER_INIT_STRUCT)10071008return XR_SUCCESS;1009}101010111012