startup_checks.cpp 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "video_core/vulkan_common/vulkan_wrapper.h"
  4. #ifdef _WIN32
  5. #include <cstring> // for memset, strncpy
  6. #include <processthreadsapi.h>
  7. #include <windows.h>
  8. #elif defined(YUZU_UNIX)
  9. #include <errno.h>
  10. #include <sys/wait.h>
  11. #include <unistd.h>
  12. #endif
  13. #include <cstdio>
  14. #include "video_core/vulkan_common/vulkan_instance.h"
  15. #include "video_core/vulkan_common/vulkan_library.h"
  16. #include "yuzu/startup_checks.h"
  17. void CheckVulkan() {
  18. // Just start the Vulkan loader, this will crash if something is wrong
  19. try {
  20. Vulkan::vk::InstanceDispatch dld;
  21. const Common::DynamicLibrary library = Vulkan::OpenLibrary();
  22. const Vulkan::vk::Instance instance =
  23. Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_1);
  24. } catch (const Vulkan::vk::Exception& exception) {
  25. std::fprintf(stderr, "Failed to initialize Vulkan: %s\n", exception.what());
  26. }
  27. }
  28. bool CheckEnvVars(bool* is_child) {
  29. #ifdef _WIN32
  30. // Check environment variable to see if we are the child
  31. char variable_contents[8];
  32. const DWORD startup_check_var =
  33. GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8);
  34. if (startup_check_var > 0 && std::strncmp(variable_contents, ENV_VAR_ENABLED_TEXT, 8) == 0) {
  35. CheckVulkan();
  36. return true;
  37. }
  38. // Don't perform startup checks if we are a child process
  39. char is_child_s[8];
  40. const DWORD is_child_len = GetEnvironmentVariableA(IS_CHILD_ENV_VAR, is_child_s, 8);
  41. if (is_child_len > 0 && std::strncmp(is_child_s, ENV_VAR_ENABLED_TEXT, 8) == 0) {
  42. *is_child = true;
  43. return false;
  44. } else if (!SetEnvironmentVariableA(IS_CHILD_ENV_VAR, ENV_VAR_ENABLED_TEXT)) {
  45. std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %lu\n",
  46. IS_CHILD_ENV_VAR, GetLastError());
  47. return true;
  48. }
  49. #endif
  50. return false;
  51. }
  52. bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check) {
  53. #ifdef _WIN32
  54. // Set the startup variable for child processes
  55. const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT);
  56. if (!env_var_set) {
  57. std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %lu\n",
  58. STARTUP_CHECK_ENV_VAR, GetLastError());
  59. return false;
  60. }
  61. if (perform_vulkan_check) {
  62. // Spawn child process that performs Vulkan check
  63. PROCESS_INFORMATION process_info;
  64. std::memset(&process_info, '\0', sizeof(process_info));
  65. if (!SpawnChild(arg0, &process_info, 0)) {
  66. return false;
  67. }
  68. // Wait until the processs exits and get exit code from it
  69. WaitForSingleObject(process_info.hProcess, INFINITE);
  70. DWORD exit_code = STILL_ACTIVE;
  71. const int err = GetExitCodeProcess(process_info.hProcess, &exit_code);
  72. if (err == 0) {
  73. std::fprintf(stderr, "GetExitCodeProcess failed with error %lu\n", GetLastError());
  74. }
  75. // Vulkan is broken if the child crashed (return value is not zero)
  76. *has_broken_vulkan = (exit_code != 0);
  77. if (CloseHandle(process_info.hProcess) == 0) {
  78. std::fprintf(stderr, "CloseHandle failed with error %lu\n", GetLastError());
  79. }
  80. if (CloseHandle(process_info.hThread) == 0) {
  81. std::fprintf(stderr, "CloseHandle failed with error %lu\n", GetLastError());
  82. }
  83. }
  84. if (!SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, nullptr)) {
  85. std::fprintf(stderr, "SetEnvironmentVariableA failed to clear %s with error %lu\n",
  86. STARTUP_CHECK_ENV_VAR, GetLastError());
  87. }
  88. #elif defined(YUZU_UNIX)
  89. if (perform_vulkan_check) {
  90. const pid_t pid = fork();
  91. if (pid == 0) {
  92. CheckVulkan();
  93. return true;
  94. } else if (pid == -1) {
  95. const int err = errno;
  96. std::fprintf(stderr, "fork failed with error %d\n", err);
  97. return false;
  98. }
  99. // Get exit code from child process
  100. int status;
  101. const int r_val = wait(&status);
  102. if (r_val == -1) {
  103. const int err = errno;
  104. std::fprintf(stderr, "wait failed with error %d\n", err);
  105. return false;
  106. }
  107. // Vulkan is broken if the child crashed (return value is not zero)
  108. *has_broken_vulkan = (status != 0);
  109. }
  110. #endif
  111. return false;
  112. }
  113. #ifdef _WIN32
  114. bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags) {
  115. STARTUPINFOA startup_info;
  116. std::memset(&startup_info, '\0', sizeof(startup_info));
  117. startup_info.cb = sizeof(startup_info);
  118. char p_name[255];
  119. std::strncpy(p_name, arg0, 254);
  120. p_name[254] = '\0';
  121. const bool process_created = CreateProcessA(nullptr, // lpApplicationName
  122. p_name, // lpCommandLine
  123. nullptr, // lpProcessAttributes
  124. nullptr, // lpThreadAttributes
  125. false, // bInheritHandles
  126. flags, // dwCreationFlags
  127. nullptr, // lpEnvironment
  128. nullptr, // lpCurrentDirectory
  129. &startup_info, // lpStartupInfo
  130. pi // lpProcessInformation
  131. );
  132. if (!process_created) {
  133. std::fprintf(stderr, "CreateProcessA failed with error %lu\n", GetLastError());
  134. return false;
  135. }
  136. return true;
  137. }
  138. #endif