Path: blob/master/thirdparty/openxr/src/loader/manifest_file.cpp
9917 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 "platform_utils.hpp"23#include "loader_logger.hpp"24#include "unique_asset.h"2526#include <json/json.h>27#include <openxr/openxr.h>2829#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>4142#ifndef FALLBACK_CONFIG_DIRS43#define FALLBACK_CONFIG_DIRS "/etc/xdg"44#endif // !FALLBACK_CONFIG_DIRS4546#ifndef FALLBACK_DATA_DIRS47#define FALLBACK_DATA_DIRS "/usr/local/share:/usr/share"48#endif // !FALLBACK_DATA_DIRS4950#ifndef SYSCONFDIR51#define SYSCONFDIR "/etc"52#endif // !SYSCONFDIR5354#ifdef XR_USE_PLATFORM_ANDROID55#include <android/asset_manager.h>56#endif5758#ifdef XRLOADER_DISABLE_EXCEPTION_HANDLING59#if JSON_USE_EXCEPTIONS60#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_EXCEPTIONS63#endif // !XRLOADER_DISABLE_EXCEPTION_HANDLING6465#include "runtime_interface.hpp"6667// Utility functions for finding files in the appropriate paths6869static inline bool StringEndsWith(const std::string &value, const std::string &ending) {70if (ending.size() > value.size()) {71return false;72}73return std::equal(ending.rbegin(), ending.rend(), value.rbegin());74}7576// If the file found is a manifest file name, add it to the out_files manifest list.77static bool AddIfJson(const std::string &full_file, std::vector<std::string> &manifest_files) {78if (full_file.empty() || !StringEndsWith(full_file, ".json")) {79return false;80}81manifest_files.push_back(full_file);82return true;83}8485// Check the current path for any manifest files. If the provided search_path is a directory, look for86// all included JSON files in that directory. Otherwise, just check the provided search_path which should87// be a single filename.88static void CheckAllFilesInThePath(const std::string &search_path, bool is_directory_list,89std::vector<std::string> &manifest_files) {90if (FileSysUtilsPathExists(search_path)) {91std::string absolute_path;92if (!is_directory_list) {93// If the file exists, try to add it94if (FileSysUtilsIsRegularFile(search_path)) {95FileSysUtilsGetAbsolutePath(search_path, absolute_path);96AddIfJson(absolute_path, manifest_files);97}98} else {99std::vector<std::string> files;100if (FileSysUtilsFindFilesInPath(search_path, files)) {101for (std::string &cur_file : files) {102std::string relative_path;103FileSysUtilsCombinePaths(search_path, cur_file, relative_path);104if (!FileSysUtilsGetAbsolutePath(relative_path, absolute_path)) {105continue;106}107AddIfJson(absolute_path, manifest_files);108}109}110}111}112}113114// Add all manifest files in the provided paths to the manifest_files list. If search_path115// is made up of directory listings (versus direct manifest file names) search each path for116// any manifest files.117static void AddFilesInPath(const std::string &search_path, bool is_directory_list, std::vector<std::string> &manifest_files) {118std::size_t last_found = 0;119std::size_t found = search_path.find_first_of(PATH_SEPARATOR);120std::string cur_search;121122// Handle any path listings in the string (separated by the appropriate path separator)123while (found != std::string::npos) {124// substr takes a start index and length.125std::size_t length = found - last_found;126cur_search = search_path.substr(last_found, length);127128CheckAllFilesInThePath(cur_search, is_directory_list, manifest_files);129130// This works around issue if multiple path separator follow each other directly.131last_found = found;132while (found == last_found) {133last_found = found + 1;134found = search_path.find_first_of(PATH_SEPARATOR, last_found);135}136}137138// If there's something remaining in the string, copy it over139if (last_found < search_path.size()) {140cur_search = search_path.substr(last_found);141CheckAllFilesInThePath(cur_search, is_directory_list, manifest_files);142}143}144145// Copy all paths listed in the cur_path string into output_path and append the appropriate relative_path onto the end of each.146static void CopyIncludedPaths(bool is_directory_list, const std::string &cur_path, const std::string &relative_path,147std::string &output_path) {148if (!cur_path.empty()) {149std::size_t last_found = 0;150std::size_t found = cur_path.find_first_of(PATH_SEPARATOR);151152// Handle any path listings in the string (separated by the appropriate path separator)153while (found != std::string::npos) {154std::size_t length = found - last_found;155output_path += cur_path.substr(last_found, length);156if (is_directory_list && (cur_path[found - 1] != '\\' && cur_path[found - 1] != '/')) {157output_path += DIRECTORY_SYMBOL;158}159output_path += relative_path;160output_path += PATH_SEPARATOR;161162last_found = found;163found = cur_path.find_first_of(PATH_SEPARATOR, found + 1);164}165166// If there's something remaining in the string, copy it over167size_t last_char = cur_path.size() - 1;168if (last_found != last_char) {169output_path += cur_path.substr(last_found);170if (is_directory_list && (cur_path[last_char] != '\\' && cur_path[last_char] != '/')) {171output_path += DIRECTORY_SYMBOL;172}173output_path += relative_path;174output_path += PATH_SEPARATOR;175}176}177}178179// Look for data files in the provided paths, but first check the environment override to determine if we should use that instead.180static void ReadDataFilesInSearchPaths(const std::string &override_env_var, const std::string &relative_path, bool &override_active,181std::vector<std::string> &manifest_files) {182std::string override_path;183std::string search_path;184185if (!override_env_var.empty()) {186bool permit_override = true;187#ifndef XR_OS_WINDOWS188if (geteuid() != getuid() || getegid() != getgid()) {189// Don't allow setuid apps to use the env var190permit_override = false;191}192#endif193if (permit_override) {194override_path = PlatformUtilsGetSecureEnv(override_env_var.c_str());195}196}197198if (!override_path.empty()) {199CopyIncludedPaths(true, override_path, "", search_path);200override_active = true;201} else {202override_active = false;203#if !defined(XR_OS_WINDOWS) && !defined(XR_OS_ANDROID)204const char home_additional[] = ".local/share/";205206// Determine how much space is needed to generate the full search path207// for the current manifest files.208std::string xdg_conf_dirs = PlatformUtilsGetSecureEnv("XDG_CONFIG_DIRS");209std::string xdg_data_dirs = PlatformUtilsGetSecureEnv("XDG_DATA_DIRS");210std::string xdg_data_home = PlatformUtilsGetSecureEnv("XDG_DATA_HOME");211std::string home = PlatformUtilsGetSecureEnv("HOME");212213if (xdg_conf_dirs.empty()) {214CopyIncludedPaths(true, FALLBACK_CONFIG_DIRS, relative_path, search_path);215} else {216CopyIncludedPaths(true, xdg_conf_dirs, relative_path, search_path);217}218219CopyIncludedPaths(true, SYSCONFDIR, relative_path, search_path);220#if defined(EXTRASYSCONFDIR)221CopyIncludedPaths(true, EXTRASYSCONFDIR, relative_path, search_path);222#endif223224if (xdg_data_dirs.empty()) {225CopyIncludedPaths(true, FALLBACK_DATA_DIRS, relative_path, search_path);226} else {227CopyIncludedPaths(true, xdg_data_dirs, relative_path, search_path);228}229230if (!xdg_data_home.empty()) {231CopyIncludedPaths(true, xdg_data_home, relative_path, search_path);232} else if (!home.empty()) {233std::string relative_home_path = home_additional;234relative_home_path += relative_path;235CopyIncludedPaths(true, home, relative_home_path, search_path);236}237#elif defined(XR_OS_ANDROID)238CopyIncludedPaths(true, "/product/etc", relative_path, search_path);239CopyIncludedPaths(true, "/odm/etc", relative_path, search_path);240CopyIncludedPaths(true, "/oem/etc", relative_path, search_path);241CopyIncludedPaths(true, "/vendor/etc", relative_path, search_path);242CopyIncludedPaths(true, "/system/etc", relative_path, search_path);243#else244(void)relative_path;245#endif246}247248// Now, parse the paths and add any manifest files found in them.249AddFilesInPath(search_path, true, manifest_files);250}251252#ifdef XR_OS_LINUX253254// Get an XDG environment variable with a $HOME-relative default255static std::string GetXDGEnvHome(const char *name, const char *fallback_path) {256std::string result = PlatformUtilsGetSecureEnv(name);257if (!result.empty()) {258return result;259}260result = PlatformUtilsGetSecureEnv("HOME");261if (result.empty()) {262return result;263}264result += "/";265result += fallback_path;266return result;267}268269// Get an XDG environment variable with absolute defaults270static std::string GetXDGEnvAbsolute(const char *name, const char *fallback_paths) {271std::string result = PlatformUtilsGetSecureEnv(name);272if (!result.empty()) {273return result;274}275return fallback_paths;276}277278/// @param rt_dir_prefix Directory prefix with a trailing slash279static bool FindEitherActiveRuntimeFilename(const char *prefix_desc, const std::string &rt_dir_prefix, uint16_t major_version,280std::string &out) {281{282std::ostringstream oss;283oss << "Looking for active_runtime." XR_ARCH_ABI ".json or active_runtime.json in ";284oss << prefix_desc;285oss << ": ";286oss << rt_dir_prefix;287288LoaderLogger::LogInfoMessage("", oss.str());289}290{291auto decorated_path = rt_dir_prefix + std::to_string(major_version) + "/active_runtime." XR_ARCH_ABI ".json";292293if (FileSysUtilsPathExists(decorated_path)) {294out = decorated_path;295return true;296}297}298{299auto undecorated_path = rt_dir_prefix + std::to_string(major_version) + "/active_runtime.json";300301if (FileSysUtilsPathExists(undecorated_path)) {302out = undecorated_path;303return true;304}305}306return false;307}308// Return the first instance of relative_path occurring in an XDG config dir according to standard309// precedence order.310static bool FindXDGConfigFile(const char *relative_dir, uint16_t major_version, std::string &out) {311const std::string message{"Looking for active_runtime." XR_ARCH_ABI ".json or active_runtime.json"};312std::string dir_prefix = GetXDGEnvHome("XDG_CONFIG_HOME", ".config");313if (!dir_prefix.empty()) {314dir_prefix += "/";315dir_prefix += relative_dir;316if (FindEitherActiveRuntimeFilename("XDG_CONFIG_HOME", dir_prefix, major_version, out)) {317return true;318}319}320321std::istringstream iss(GetXDGEnvAbsolute("XDG_CONFIG_DIRS", FALLBACK_CONFIG_DIRS));322std::string path;323while (std::getline(iss, path, PATH_SEPARATOR)) {324if (path.empty()) {325continue;326}327dir_prefix = std::move(path);328dir_prefix += "/";329dir_prefix += relative_dir;330if (FindEitherActiveRuntimeFilename("an entry of XDG_CONFIG_DIRS", dir_prefix, major_version, out)) {331return true;332}333}334335dir_prefix = SYSCONFDIR;336dir_prefix += "/";337dir_prefix += relative_dir;338if (FindEitherActiveRuntimeFilename("compiled-in SYSCONFDIR", dir_prefix, major_version, out)) {339return true;340}341342#if defined(EXTRASYSCONFDIR)343dir_prefix = EXTRASYSCONFDIR;344dir_prefix += "/";345dir_prefix += relative_dir;346if (FindEitherActiveRuntimeFilename("compiled-in EXTRASYSCONFDIR", dir_prefix, major_version, out)) {347return true;348}349#endif350351out.clear();352return false;353}354355#endif356357#ifdef XR_OS_WINDOWS358359// Look for runtime data files in the provided paths, but first check the environment override to determine360// if we should use that instead.361static void ReadRuntimeDataFilesInRegistry(const std::string &runtime_registry_location,362const std::string &default_runtime_value_name,363std::vector<std::string> &manifest_files) {364HKEY hkey;365DWORD access_flags;366wchar_t value_w[1024];367DWORD value_size_w = sizeof(value_w); // byte size of the buffer.368369// Generate the full registry location for the registry information370std::string full_registry_location = OPENXR_REGISTRY_LOCATION;371full_registry_location += std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION));372full_registry_location += runtime_registry_location;373374const std::wstring full_registry_location_w = utf8_to_wide(full_registry_location);375const std::wstring default_runtime_value_name_w = utf8_to_wide(default_runtime_value_name);376377// Use 64 bit regkey for 64bit application, and use 32 bit regkey in WOW for 32bit application.378access_flags = KEY_QUERY_VALUE;379LONG open_value = RegOpenKeyExW(HKEY_LOCAL_MACHINE, full_registry_location_w.c_str(), 0, access_flags, &hkey);380381if (ERROR_SUCCESS != open_value) {382LoaderLogger::LogWarningMessage("",383"ReadRuntimeDataFilesInRegistry - failed to open registry key " + full_registry_location);384return;385}386387if (ERROR_SUCCESS != RegGetValueW(hkey, nullptr, default_runtime_value_name_w.c_str(),388RRF_RT_REG_SZ | REG_EXPAND_SZ | RRF_ZEROONFAILURE, NULL, reinterpret_cast<LPBYTE>(&value_w),389&value_size_w)) {390LoaderLogger::LogWarningMessage(391"", "ReadRuntimeDataFilesInRegistry - failed to read registry value " + default_runtime_value_name);392} else {393// Not using AddFilesInPath here (as only api_layer manifest paths allow multiple394// separated paths)395// Small time-of-check vs time-of-use issue here but it mainly only affects the error message.396// It does not introduce a security defect.397std::string activeRuntimePath = wide_to_utf8(value_w);398if (FileSysUtilsIsRegularFile(activeRuntimePath)) {399// If the file exists, try to add it400std::string absolute_path;401FileSysUtilsGetAbsolutePath(activeRuntimePath, absolute_path);402if (!AddIfJson(absolute_path, manifest_files)) {403LoaderLogger::LogErrorMessage(404"", "ReadRuntimeDataFilesInRegistry - registry runtime path is not json " + activeRuntimePath);405}406} else {407LoaderLogger::LogErrorMessage(408"", "ReadRuntimeDataFilesInRegistry - registry runtime path does not exist " + activeRuntimePath);409}410}411412RegCloseKey(hkey);413}414415// Look for layer data files in the provided paths, but first check the environment override to determine416// if we should use that instead.417static void ReadLayerDataFilesInRegistry(const std::string ®istry_location, std::vector<std::string> &manifest_files) {418const std::wstring full_registry_location_w =419utf8_to_wide(OPENXR_REGISTRY_LOCATION + std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) + registry_location);420421auto ReadLayerDataFilesInHive = [&](HKEY hive) {422HKEY hkey;423LONG open_value = RegOpenKeyExW(hive, full_registry_location_w.c_str(), 0, KEY_QUERY_VALUE, &hkey);424if (ERROR_SUCCESS != open_value) {425return false;426}427428wchar_t name_w[1024]{};429LONG rtn_value;430DWORD name_size = 1023;431DWORD value;432DWORD value_size = sizeof(value);433DWORD key_index = 0;434while (ERROR_SUCCESS ==435(rtn_value = RegEnumValueW(hkey, key_index++, name_w, &name_size, NULL, NULL, (LPBYTE)&value, &value_size))) {436if (value_size == sizeof(value) && value == 0) {437const std::string filename = wide_to_utf8(name_w);438AddFilesInPath(filename, false, manifest_files);439}440// Reset some items for the next loop441name_size = 1023;442}443444RegCloseKey(hkey);445446return true;447};448449// Do not allow high integrity processes to act on data that can be controlled by medium integrity processes.450const bool readFromCurrentUser = !IsHighIntegrityLevel();451452bool found = ReadLayerDataFilesInHive(HKEY_LOCAL_MACHINE);453if (readFromCurrentUser) {454found |= ReadLayerDataFilesInHive(HKEY_CURRENT_USER);455}456457if (!found) {458std::string warning_message = "ReadLayerDataFilesInRegistry - failed to read registry location ";459warning_message += registry_location;460warning_message += (readFromCurrentUser ? " in either HKEY_LOCAL_MACHINE or HKEY_CURRENT_USER" : " in HKEY_LOCAL_MACHINE");461LoaderLogger::LogWarningMessage("", warning_message);462}463}464465#endif // XR_OS_WINDOWS466467ManifestFile::ManifestFile(ManifestFileType type, const std::string &filename, const std::string &library_path)468: _filename(filename), _type(type), _library_path(library_path) {}469470bool ManifestFile::IsValidJson(const Json::Value &root_node, JsonVersion &version) {471if (root_node["file_format_version"].isNull() || !root_node["file_format_version"].isString()) {472LoaderLogger::LogErrorMessage("", "ManifestFile::IsValidJson - JSON file missing \"file_format_version\"");473return false;474}475std::string file_format = root_node["file_format_version"].asString();476const int num_fields = sscanf(file_format.c_str(), "%u.%u.%u", &version.major, &version.minor, &version.patch);477478// Only version 1.0.0 is defined currently. Eventually we may have more version, but479// some of the versions may only be valid for layers or runtimes specifically.480if (num_fields != 3 || version.major != 1 || version.minor != 0 || version.patch != 0) {481std::ostringstream error_ss;482error_ss << "ManifestFile::IsValidJson - JSON \"file_format_version\" " << version.major << "." << version.minor << "."483<< version.patch << " is not supported";484LoaderLogger::LogErrorMessage("", error_ss.str());485return false;486}487488return true;489}490491static void GetExtensionProperties(const std::vector<ExtensionListing> &extensions, std::vector<XrExtensionProperties> &props) {492for (const auto &ext : extensions) {493auto it =494std::find_if(props.begin(), props.end(), [&](XrExtensionProperties &prop) { return prop.extensionName == ext.name; });495if (it != props.end()) {496it->extensionVersion = std::max(it->extensionVersion, ext.extension_version);497} else {498XrExtensionProperties prop{};499prop.type = XR_TYPE_EXTENSION_PROPERTIES;500strncpy(prop.extensionName, ext.name.c_str(), XR_MAX_EXTENSION_NAME_SIZE - 1);501prop.extensionName[XR_MAX_EXTENSION_NAME_SIZE - 1] = '\0';502prop.extensionVersion = ext.extension_version;503props.push_back(prop);504}505}506}507508// Return any instance extensions found in the manifest files in the proper form for509// OpenXR (XrExtensionProperties).510void ManifestFile::GetInstanceExtensionProperties(std::vector<XrExtensionProperties> &props) {511GetExtensionProperties(_instance_extensions, props);512}513514const std::string &ManifestFile::GetFunctionName(const std::string &func_name) const {515if (!_functions_renamed.empty()) {516auto found = _functions_renamed.find(func_name);517if (found != _functions_renamed.end()) {518return found->second;519}520}521return func_name;522}523524RuntimeManifestFile::RuntimeManifestFile(const std::string &filename, const std::string &library_path)525: ManifestFile(MANIFEST_TYPE_RUNTIME, filename, library_path) {}526527static void ParseExtension(Json::Value const &ext, std::vector<ExtensionListing> &extensions) {528Json::Value ext_name = ext["name"];529Json::Value ext_version = ext["extension_version"];530531// Allow "extension_version" as a String or a UInt to maintain backwards compatibility, even though it should be a String.532// Internal Issue 1411: https://gitlab.khronos.org/openxr/openxr/-/issues/1411533// Internal MR !1867: https://gitlab.khronos.org/openxr/openxr/-/merge_requests/1867534if (ext_name.isString() && (ext_version.isString() || ext_version.isUInt())) {535ExtensionListing ext_listing = {};536ext_listing.name = ext_name.asString();537if (ext_version.isUInt()) {538ext_listing.extension_version = ext_version.asUInt();539} else {540ext_listing.extension_version = atoi(ext_version.asString().c_str());541}542extensions.push_back(ext_listing);543}544}545546void ManifestFile::ParseCommon(Json::Value const &root_node) {547const Json::Value &inst_exts = root_node["instance_extensions"];548if (!inst_exts.isNull() && inst_exts.isArray()) {549for (const auto &ext : inst_exts) {550ParseExtension(ext, _instance_extensions);551}552}553const Json::Value &funcs_renamed = root_node["functions"];554if (!funcs_renamed.isNull() && !funcs_renamed.empty()) {555for (Json::ValueConstIterator func_it = funcs_renamed.begin(); func_it != funcs_renamed.end(); ++func_it) {556if (!(*func_it).isString()) {557LoaderLogger::LogWarningMessage(558"", "ManifestFile::ParseCommon " + _filename + " \"functions\" section contains non-string values.");559continue;560}561std::string original_name = func_it.key().asString();562std::string new_name = (*func_it).asString();563_functions_renamed.emplace(original_name, new_name);564}565}566}567568void RuntimeManifestFile::CreateIfValid(std::string const &filename,569std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) {570std::ifstream json_stream(filename, std::ifstream::in);571572LoaderLogger::LogInfoMessage("", "RuntimeManifestFile::CreateIfValid - attempting to load " + filename);573std::ostringstream error_ss("RuntimeManifestFile::CreateIfValid ");574if (!json_stream.is_open()) {575error_ss << "failed to open " << filename << ". Does it exist?";576LoaderLogger::LogErrorMessage("", error_ss.str());577return;578}579Json::CharReaderBuilder builder;580std::string errors;581Json::Value root_node = Json::nullValue;582if (!Json::parseFromStream(builder, json_stream, &root_node, &errors) || !root_node.isObject()) {583error_ss << "failed to parse " << filename << ".";584if (!errors.empty()) {585error_ss << " (Error message: " << errors << ")";586}587error_ss << " Is it a valid runtime manifest file?";588LoaderLogger::LogErrorMessage("", error_ss.str());589return;590}591592CreateIfValid(root_node, filename, manifest_files);593}594595void RuntimeManifestFile::CreateIfValid(const Json::Value &root_node, const std::string &filename,596std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) {597std::ostringstream error_ss("RuntimeManifestFile::CreateIfValid ");598JsonVersion file_version = {};599if (!ManifestFile::IsValidJson(root_node, file_version)) {600error_ss << "isValidJson indicates " << filename << " is not a valid manifest file.";601LoaderLogger::LogErrorMessage("", error_ss.str());602return;603}604const Json::Value &runtime_root_node = root_node["runtime"];605// The Runtime manifest file needs the "runtime" root as well as a sub-node for "library_path". If any of those aren't there,606// fail.607if (runtime_root_node.isNull() || runtime_root_node["library_path"].isNull() || !runtime_root_node["library_path"].isString()) {608error_ss << filename << " is missing required fields. Verify all proper fields exist.";609LoaderLogger::LogErrorMessage("", error_ss.str());610return;611}612613std::string lib_path = runtime_root_node["library_path"].asString();614615// If the library_path variable has no directory symbol, it's just a file name and should be accessible on the616// global library path.617if (lib_path.find('\\') != std::string::npos || lib_path.find('/') != std::string::npos) {618// If the library_path is an absolute path, just use that as-is.619if (!FileSysUtilsIsAbsolutePath(lib_path)) {620// Otherwise, treat the library path as a relative path based on the JSON file.621std::string canonical_path;622std::string combined_path;623std::string file_parent;624// Search relative to the real manifest file, not relative to the symlink625if (!FileSysUtilsGetCanonicalPath(filename, canonical_path)) {626// Give relative to the non-canonical path a chance627canonical_path = filename;628}629if (!FileSysUtilsGetParentPath(canonical_path, file_parent) ||630!FileSysUtilsCombinePaths(file_parent, lib_path, combined_path)) {631error_ss << filename << " filesystem operations failed for path " << canonical_path;632LoaderLogger::LogErrorMessage("", error_ss.str());633return;634}635lib_path = combined_path;636}637}638639// Add this runtime manifest file640manifest_files.emplace_back(new RuntimeManifestFile(filename, lib_path));641642// Add any extensions to it after the fact.643// Handle any renamed functions644manifest_files.back()->ParseCommon(runtime_root_node);645}646647// Find all manifest files in the appropriate search paths/registries for the given type.648XrResult RuntimeManifestFile::FindManifestFiles(const std::string &openxr_command,649std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files) {650XrResult result = XR_SUCCESS;651std::string filename = PlatformUtilsGetSecureEnv(OPENXR_RUNTIME_JSON_ENV_VAR);652if (!filename.empty()) {653LoaderLogger::LogInfoMessage(654openxr_command,655"RuntimeManifestFile::FindManifestFiles - using environment variable override runtime file " + filename);656} else {657#ifdef XR_OS_WINDOWS658std::vector<std::string> filenames;659ReadRuntimeDataFilesInRegistry("", "ActiveRuntime", filenames);660if (filenames.size() == 0) {661LoaderLogger::LogErrorMessage(662openxr_command, "RuntimeManifestFile::FindManifestFiles - failed to find active runtime file in registry");663return XR_ERROR_RUNTIME_UNAVAILABLE;664}665if (filenames.size() > 1) {666LoaderLogger::LogWarningMessage(667openxr_command, "RuntimeManifestFile::FindManifestFiles - found too many default runtime files in registry");668}669filename = filenames[0];670LoaderLogger::LogInfoMessage(openxr_command,671"RuntimeManifestFile::FindManifestFiles - using registry-specified runtime file " + filename);672#elif defined(XR_OS_LINUX)673674if (!FindXDGConfigFile("openxr/", XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), filename)) {675LoaderLogger::LogErrorMessage(676openxr_command,677"RuntimeManifestFile::FindManifestFiles - failed to determine active runtime file path for this environment");678return XR_ERROR_RUNTIME_UNAVAILABLE;679}680#else // !defined(XR_OS_WINDOWS) && !defined(XR_OS_LINUX)681682#if defined(XR_KHR_LOADER_INIT_SUPPORT) && defined(XR_USE_PLATFORM_ANDROID)683Json::Value virtualManifest;684result = GetPlatformRuntimeVirtualManifest(virtualManifest);685if (XR_SUCCESS == result) {686RuntimeManifestFile::CreateIfValid(virtualManifest, "", manifest_files);687return result;688}689#endif // defined(XR_USE_PLATFORM_ANDROID) && defined(XR_KHR_LOADER_INIT_SUPPORT)690if (!PlatformGetGlobalRuntimeFileName(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), filename)) {691LoaderLogger::LogErrorMessage(692openxr_command,693"RuntimeManifestFile::FindManifestFiles - failed to determine active runtime file path for this environment");694return XR_ERROR_RUNTIME_UNAVAILABLE;695}696result = XR_SUCCESS;697LoaderLogger::LogInfoMessage(openxr_command,698"RuntimeManifestFile::FindManifestFiles - using global runtime file " + filename);699#endif // !defined(XR_OS_WINDOWS) && !defined(XR_OS_LINUX)700}701RuntimeManifestFile::CreateIfValid(filename, manifest_files);702703return result;704}705706ApiLayerManifestFile::ApiLayerManifestFile(ManifestFileType type, const std::string &filename, const std::string &layer_name,707const std::string &description, const JsonVersion &api_version,708const uint32_t &implementation_version, const std::string &library_path)709: ManifestFile(type, filename, library_path),710_api_version(api_version),711_layer_name(layer_name),712_description(description),713_implementation_version(implementation_version) {}714715#if defined(XR_KHR_LOADER_INIT_SUPPORT) && defined(XR_USE_PLATFORM_ANDROID)716void ApiLayerManifestFile::AddManifestFilesAndroid(const std::string &openxr_command, ManifestFileType type,717std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {718if (!LoaderInitData::instance().initialized()) {719// This will happen for applications that do not call xrInitializeLoaderKHR720LoaderLogger::LogWarningMessage(721openxr_command,722"ApiLayerManifestFile::AddManifestFilesAndroid unable to add manifest files LoaderInitData not initialized.");723return;724}725726AAssetManager *assetManager = (AAssetManager *)Android_Get_Asset_Manager();727std::vector<std::string> filenames;728{729std::string search_path = "";730switch (type) {731case MANIFEST_TYPE_IMPLICIT_API_LAYER:732search_path = "openxr/1/api_layers/implicit.d/";733break;734case MANIFEST_TYPE_EXPLICIT_API_LAYER:735search_path = "openxr/1/api_layers/explicit.d/";736break;737default:738return;739}740741UniqueAssetDir dir{AAssetManager_openDir(assetManager, search_path.c_str())};742if (!dir) {743return;744}745const std::string json = ".json";746const char *fn = nullptr;747while ((fn = AAssetDir_getNextFileName(dir.get())) != nullptr) {748const std::string filename = search_path + fn;749if (filename.size() < json.size()) {750continue;751}752if (filename.compare(filename.size() - json.size(), json.size(), json) == 0) {753filenames.push_back(filename);754}755}756}757for (const auto &filename : filenames) {758UniqueAsset asset{AAssetManager_open(assetManager, filename.c_str(), AASSET_MODE_BUFFER)};759if (!asset) {760LoaderLogger::LogWarningMessage(761openxr_command, "ApiLayerManifestFile::AddManifestFilesAndroid unable to open asset " + filename + ", skipping");762763continue;764}765size_t length = AAsset_getLength(asset.get());766const char *buf = reinterpret_cast<const char *>(AAsset_getBuffer(asset.get()));767if (!buf) {768LoaderLogger::LogWarningMessage(769openxr_command, "ApiLayerManifestFile::AddManifestFilesAndroid unable to access asset" + filename + ", skipping");770771continue;772}773std::istringstream json_stream(std::string{buf, length});774775CreateIfValid(type, filename, json_stream, &ApiLayerManifestFile::LocateLibraryInAssets, manifest_files);776}777}778#endif // defined(XR_USE_PLATFORM_ANDROID) && defined(XR_KHR_LOADER_INIT_SUPPORT)779780void ApiLayerManifestFile::CreateIfValid(ManifestFileType type, const std::string &filename, std::istream &json_stream,781LibraryLocator locate_library,782std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {783std::ostringstream error_ss("ApiLayerManifestFile::CreateIfValid ");784Json::CharReaderBuilder builder;785std::string errors;786Json::Value root_node = Json::nullValue;787if (!Json::parseFromStream(builder, json_stream, &root_node, &errors) || !root_node.isObject()) {788error_ss << "failed to parse " << filename << ".";789if (!errors.empty()) {790error_ss << " (Error message: " << errors << ")";791}792error_ss << " Is it a valid layer manifest file?";793LoaderLogger::LogErrorMessage("", error_ss.str());794return;795}796JsonVersion file_version = {};797if (!ManifestFile::IsValidJson(root_node, file_version)) {798error_ss << "isValidJson indicates " << filename << " is not a valid manifest file.";799LoaderLogger::LogErrorMessage("", error_ss.str());800return;801}802803Json::Value layer_root_node = root_node["api_layer"];804805// The API Layer manifest file needs the "api_layer" root as well as other sub-nodes.806// If any of those aren't there, fail.807if (layer_root_node.isNull() || layer_root_node["name"].isNull() || !layer_root_node["name"].isString() ||808layer_root_node["api_version"].isNull() || !layer_root_node["api_version"].isString() ||809layer_root_node["library_path"].isNull() || !layer_root_node["library_path"].isString() ||810layer_root_node["implementation_version"].isNull() || !layer_root_node["implementation_version"].isString()) {811error_ss << filename << " is missing required fields. Verify all proper fields exist.";812LoaderLogger::LogErrorMessage("", error_ss.str());813return;814}815if (MANIFEST_TYPE_IMPLICIT_API_LAYER == type) {816bool enabled = true;817// Implicit layers require the disable environment variable.818if (layer_root_node["disable_environment"].isNull() || !layer_root_node["disable_environment"].isString()) {819error_ss << "Implicit layer " << filename << " is missing \"disable_environment\"";820LoaderLogger::LogErrorMessage("", error_ss.str());821return;822}823// Check if there's an enable environment variable provided824if (!layer_root_node["enable_environment"].isNull() && layer_root_node["enable_environment"].isString()) {825std::string env_var = layer_root_node["enable_environment"].asString();826// If it's not set in the environment, disable the layer827if (!PlatformUtilsGetEnvSet(env_var.c_str())) {828enabled = false;829}830}831// Check for the disable environment variable, which must be provided in the JSON832std::string env_var = layer_root_node["disable_environment"].asString();833// If the env var is set, disable the layer. Disable env var overrides enable above834if (PlatformUtilsGetEnvSet(env_var.c_str())) {835enabled = false;836}837838// Not enabled, so pretend like it isn't even there.839if (!enabled) {840error_ss << "Implicit layer " << filename << " is disabled";841LoaderLogger::LogInfoMessage("", error_ss.str());842return;843}844}845std::string layer_name = layer_root_node["name"].asString();846std::string api_version_string = layer_root_node["api_version"].asString();847JsonVersion api_version = {};848const int num_fields = sscanf(api_version_string.c_str(), "%u.%u", &api_version.major, &api_version.minor);849api_version.patch = 0;850851if ((num_fields != 2) || (api_version.major == 0 && api_version.minor == 0) ||852api_version.major > XR_VERSION_MAJOR(XR_CURRENT_API_VERSION)) {853error_ss << "layer " << filename << " has invalid API Version. Skipping layer.";854LoaderLogger::LogWarningMessage("", error_ss.str());855return;856}857858uint32_t implementation_version = atoi(layer_root_node["implementation_version"].asString().c_str());859std::string library_path = layer_root_node["library_path"].asString();860861// If the library_path variable has no directory symbol, it's just a file name and should be accessible on the862// global library path.863if (library_path.find('\\') != std::string::npos || library_path.find('/') != std::string::npos) {864// If the library_path is an absolute path, just use that if it exists865if (FileSysUtilsIsAbsolutePath(library_path)) {866if (!FileSysUtilsPathExists(library_path)) {867error_ss << filename << " library " << library_path << " does not appear to exist";868LoaderLogger::LogErrorMessage("", error_ss.str());869return;870}871} else {872// Otherwise, treat the library path as a relative path based on the JSON file.873std::string combined_path;874if (!locate_library(filename, library_path, combined_path)) {875error_ss << filename << " library " << combined_path << " does not appear to exist";876LoaderLogger::LogErrorMessage("", error_ss.str());877return;878}879library_path = combined_path;880}881}882883std::string description;884if (!layer_root_node["description"].isNull() && layer_root_node["description"].isString()) {885description = layer_root_node["description"].asString();886}887888// Add this layer manifest file889manifest_files.emplace_back(890new ApiLayerManifestFile(type, filename, layer_name, description, api_version, implementation_version, library_path));891892// Add any extensions to it after the fact.893manifest_files.back()->ParseCommon(layer_root_node);894}895896void ApiLayerManifestFile::CreateIfValid(ManifestFileType type, const std::string &filename,897std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {898std::ifstream json_stream(filename, std::ifstream::in);899if (!json_stream.is_open()) {900std::ostringstream error_ss("ApiLayerManifestFile::CreateIfValid ");901error_ss << "failed to open " << filename << ". Does it exist?";902LoaderLogger::LogErrorMessage("", error_ss.str());903return;904}905CreateIfValid(type, filename, json_stream, &ApiLayerManifestFile::LocateLibraryRelativeToJson, manifest_files);906}907908bool ApiLayerManifestFile::LocateLibraryRelativeToJson(909const std::string &json_filename, const std::string &library_path,910std::string &out_combined_path) { // Otherwise, treat the library path as a relative path based on the JSON file.911std::string combined_path;912std::string file_parent;913if (!FileSysUtilsGetParentPath(json_filename, file_parent) ||914!FileSysUtilsCombinePaths(file_parent, library_path, combined_path) || !FileSysUtilsPathExists(combined_path)) {915out_combined_path = combined_path;916return false;917}918out_combined_path = combined_path;919return true;920}921922#if defined(XR_KHR_LOADER_INIT_SUPPORT) && defined(XR_USE_PLATFORM_ANDROID)923bool ApiLayerManifestFile::LocateLibraryInAssets(const std::string & /* json_filename */, const std::string &library_path,924std::string &out_combined_path) {925std::string combined_path;926std::string file_parent = GetAndroidNativeLibraryDir();927if (!FileSysUtilsCombinePaths(file_parent, library_path, combined_path) || !FileSysUtilsPathExists(combined_path)) {928out_combined_path = combined_path;929return false;930}931out_combined_path = combined_path;932return true;933}934#endif // defined(XR_USE_PLATFORM_ANDROID) && defined(XR_KHR_LOADER_INIT_SUPPORT)935936void ApiLayerManifestFile::PopulateApiLayerProperties(XrApiLayerProperties &props) const {937props.layerVersion = _implementation_version;938props.specVersion = XR_MAKE_VERSION(_api_version.major, _api_version.minor, _api_version.patch);939strncpy(props.layerName, _layer_name.c_str(), XR_MAX_API_LAYER_NAME_SIZE - 1);940if (_layer_name.size() >= XR_MAX_API_LAYER_NAME_SIZE - 1) {941props.layerName[XR_MAX_API_LAYER_NAME_SIZE - 1] = '\0';942}943strncpy(props.description, _description.c_str(), XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1);944if (_description.size() >= XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1) {945props.description[XR_MAX_API_LAYER_DESCRIPTION_SIZE - 1] = '\0';946}947}948949// Find all layer manifest files in the appropriate search paths/registries for the given type.950XrResult ApiLayerManifestFile::FindManifestFiles(const std::string &openxr_command, ManifestFileType type,951std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files) {952std::string relative_path;953std::string override_env_var;954std::string registry_location;955956// Add the appropriate top-level folders for the relative path. These should be957// the string "openxr/" followed by the API major version as a string.958relative_path = OPENXR_RELATIVE_PATH;959relative_path += std::to_string(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION));960961switch (type) {962case MANIFEST_TYPE_IMPLICIT_API_LAYER:963relative_path += OPENXR_IMPLICIT_API_LAYER_RELATIVE_PATH;964override_env_var = "";965#ifdef XR_OS_WINDOWS966registry_location = OPENXR_IMPLICIT_API_LAYER_REGISTRY_LOCATION;967#endif968break;969case MANIFEST_TYPE_EXPLICIT_API_LAYER:970relative_path += OPENXR_EXPLICIT_API_LAYER_RELATIVE_PATH;971override_env_var = OPENXR_API_LAYER_PATH_ENV_VAR;972#ifdef XR_OS_WINDOWS973registry_location = OPENXR_EXPLICIT_API_LAYER_REGISTRY_LOCATION;974#endif975break;976default:977LoaderLogger::LogErrorMessage(openxr_command,978"ApiLayerManifestFile::FindManifestFiles - unknown manifest file requested");979return XR_ERROR_FILE_ACCESS_ERROR;980}981982bool override_active = false;983std::vector<std::string> filenames;984ReadDataFilesInSearchPaths(override_env_var, relative_path, override_active, filenames);985986#ifdef XR_OS_WINDOWS987// Read the registry if the override wasn't active.988if (!override_active) {989ReadLayerDataFilesInRegistry(registry_location, filenames);990}991#endif992993for (std::string &cur_file : filenames) {994ApiLayerManifestFile::CreateIfValid(type, cur_file, manifest_files);995}996997#if defined(XR_KHR_LOADER_INIT_SUPPORT) && defined(XR_USE_PLATFORM_ANDROID)998ApiLayerManifestFile::AddManifestFilesAndroid(openxr_command, type, manifest_files);999#endif // defined(XR_USE_PLATFORM_ANDROID) && defined(XR_KHR_LOADER_INIT_SUPPORT)10001001return XR_SUCCESS;1002}100310041005