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 | |