Modify and restore NVIDIA control panel settings

Namely:
- Power profile for sunshine.exe
- Global OpenGL/Vulkan DXGI swapchain
This commit is contained in:
ns6089 2023-07-02 13:01:29 +03:00 committed by Cameron Gutman
parent 6992d424a8
commit f245f777f7
15 changed files with 1174 additions and 4 deletions

View File

@ -134,10 +134,12 @@ pkg_check_modules(CURL REQUIRED libcurl)
if(WIN32)
set(Boost_USE_STATIC_LIBS ON) # cmake-lint: disable=C0103
# 1.82.0+ is required for boost::json::value::set_at_pointer() support
find_package(Boost 1.82.0 COMPONENTS locale log filesystem program_options json REQUIRED)
else()
find_package(Boost COMPONENTS locale log filesystem program_options REQUIRED)
endif()
find_package(Boost COMPONENTS locale log filesystem program_options REQUIRED)
list(APPEND SUNSHINE_COMPILE_OPTIONS -Wall -Wno-sign-compare)
# enable system tray, we will disable this later if we cannot find the required package config on linux
@ -155,6 +157,12 @@ if(WIN32)
add_compile_definitions(SUNSHINE_PLATFORM="windows")
add_subdirectory(tools) # This is temporary, only tools for Windows are needed, for now
include_directories(SYSTEM third-party/nvapi-opensource)
file(GLOB NVPREFS_FILES CONFIGURE_DEPENDS
"third-party/nvapi-opensource/*.h"
"src/platform/windows/nvprefs/*.cpp"
"src/platform/windows/nvprefs/*.h")
include_directories(SYSTEM third-party/ViGEmClient/include)
if(NOT DEFINED SUNSHINE_ICON_PATH)
@ -177,7 +185,8 @@ if(WIN32)
third-party/ViGEmClient/include/ViGEm/Client.h
third-party/ViGEmClient/include/ViGEm/Common.h
third-party/ViGEmClient/include/ViGEm/Util.h
third-party/ViGEmClient/include/ViGEm/km/BusShared.h)
third-party/ViGEmClient/include/ViGEm/km/BusShared.h
${NVPREFS_FILES})
set(OPENSSL_LIBRARIES
libssl.a
@ -762,6 +771,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
"${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS}
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\delete-firewall-rule.bat\\\"'
nsExec::ExecToLog '\\\"$INSTDIR\\\\scripts\\\\uninstall-service.bat\\\"'
nsExec::ExecToLog '\\\"$INSTDIR\\\\sunshine.exe\\\" --restore-nvprefs-undo'
MessageBox MB_YESNO|MB_ICONQUESTION \
'Do you want to remove ViGEmBus)?' \
/SD IDNO IDNO NoVigem

View File

@ -46,6 +46,11 @@ safe::mail_t mail::man;
using namespace std::literals;
namespace bl = boost::log;
#ifdef _WIN32
// Define global singleton used for NVIDIA control panel modifications
nvprefs::nvprefs_interface nvprefs_instance;
#endif
thread_pool_util::ThreadPool task_pool;
bl::sources::severity_logger<int> verbose(0); // Dominating output
bl::sources::severity_logger<int> debug(1); // Follow what is happening
@ -112,6 +117,22 @@ namespace version {
}
} // namespace version
#ifdef _WIN32
namespace restore_nvprefs_undo {
int
entry(const char *name, int argc, char *argv[]) {
// Restore global NVIDIA control panel settings to the undo file
// left by improper termination of sunshine.exe, if it exists.
// This entry point is typically called by the uninstaller.
if (nvprefs_instance.load()) {
nvprefs_instance.restore_from_and_delete_undo_file_if_exists();
nvprefs_instance.unload();
}
return 0;
}
} // namespace restore_nvprefs_undo
#endif
namespace lifetime {
static char **argv;
static std::atomic_int desired_exit_code;
@ -413,7 +434,10 @@ namespace gen_creds {
std::map<std::string_view, std::function<int(const char *name, int argc, char **argv)>> cmd_to_func {
{ "creds"sv, gen_creds::entry },
{ "help"sv, help::entry },
{ "version"sv, version::entry }
{ "version"sv, version::entry },
#ifdef _WIN32
{ "restore-nvprefs-undo"sv, restore_nvprefs_undo::entry },
#endif
};
#ifdef _WIN32
@ -568,6 +592,21 @@ main(int argc, char *argv[]) {
return fn->second(argv[0], config::sunshine.cmd.argc, config::sunshine.cmd.argv);
}
#ifdef WIN32
// Modify relevant NVIDIA control panel settings if the system has corresponding gpu
if (nvprefs_instance.load()) {
// Restore global settings to the undo file left by improper termination of sunshine.exe
nvprefs_instance.restore_from_and_delete_undo_file_if_exists();
// Modify application settings for sunshine.exe
nvprefs_instance.modify_application_profile();
// Modify global settings, undo file is produced in the process to restore after improper termination
nvprefs_instance.modify_global_profile();
// Unload dynamic library to survive driver reinstallation
nvprefs_instance.unload();
}
#endif
BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VER << std::endl;
task_pool.start(1);
@ -675,6 +714,14 @@ main(int argc, char *argv[]) {
system_tray::end_tray();
#endif
#ifdef WIN32
// Restore global NVIDIA control panel settings
if (nvprefs_instance.owning_undo_file() && nvprefs_instance.load()) {
nvprefs_instance.restore_global_profile();
nvprefs_instance.unload();
}
#endif
return lifetime::desired_exit_code;
}

View File

@ -17,6 +17,12 @@
#include "thread_pool.h"
#include "thread_safe.h"
#ifdef _WIN32
// Declare global singleton used for NVIDIA control panel modifications
#include "platform/windows/nvprefs/nvprefs_interface.h"
extern nvprefs::nvprefs_interface nvprefs_instance;
#endif
extern thread_pool_util::ThreadPool task_pool;
extern bool display_cursor;

View File

@ -33,6 +33,8 @@
#include "src/utility.h"
#include <iterator>
#include "nvprefs/nvprefs_interface.h"
// UDP_SEND_MSG_SIZE was added in the Windows 10 20H1 SDK
#ifndef UDP_SEND_MSG_SIZE
#define UDP_SEND_MSG_SIZE 2
@ -740,6 +742,16 @@ namespace platf {
// Promote ourselves to high priority class
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
// Modify NVIDIA control panel settings again, in case they have been changed externally since sunshine launch
if (nvprefs_instance.load()) {
if (!nvprefs_instance.owning_undo_file()) {
nvprefs_instance.restore_from_and_delete_undo_file_if_exists();
}
nvprefs_instance.modify_application_profile();
nvprefs_instance.modify_global_profile();
nvprefs_instance.unload();
}
// Enable low latency mode on all connected WLAN NICs if wlanapi.dll is available
if (fn_WlanOpenHandle) {
DWORD negotiated_version;

View File

@ -0,0 +1,289 @@
#include "nvprefs_common.h"
#include "driver_settings.h"
namespace {
const auto sunshine_application_profile_name = L"SunshineStream";
const auto sunshine_application_path = L"sunshine.exe";
void
nvapi_error_message(NvAPI_Status status) {
NvAPI_ShortString message = {};
NvAPI_GetErrorMessage(status, message);
nvprefs::error_message(std::string("NvAPI error: ") + message);
}
void
fill_nvapi_string(NvAPI_UnicodeString &dest, const wchar_t *src) {
static_assert(sizeof(NvU16) == sizeof(wchar_t));
memcpy_s(dest, NVAPI_UNICODE_STRING_MAX * sizeof(NvU16), src, (wcslen(src) + 1) * sizeof(wchar_t));
}
} // namespace
namespace nvprefs {
driver_settings_t::~driver_settings_t() {
if (session_handle) {
NvAPI_DRS_DestroySession(session_handle);
}
}
bool
driver_settings_t::init() {
if (session_handle) return true;
NvAPI_Status status;
status = NvAPI_Initialize();
if (status != NVAPI_OK) {
info_message("NvAPI_Initialize() failed, ignore if you don't have NVIDIA video card");
return false;
}
status = NvAPI_DRS_CreateSession(&session_handle);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_CreateSession() failed");
return false;
}
return load_settings();
}
void
driver_settings_t::destroy() {
if (session_handle) {
NvAPI_DRS_DestroySession(session_handle);
session_handle = 0;
}
NvAPI_Unload();
}
bool
driver_settings_t::load_settings() {
if (!session_handle) return false;
NvAPI_Status status = NvAPI_DRS_LoadSettings(session_handle);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_LoadSettings() failed");
destroy();
return false;
}
return true;
}
bool
driver_settings_t::save_settings() {
if (!session_handle) return false;
NvAPI_Status status = NvAPI_DRS_SaveSettings(session_handle);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_SaveSettings() failed");
return false;
}
return true;
}
bool
driver_settings_t::restore_global_profile_to_undo(const undo_data_t &undo_data) {
if (!session_handle) return false;
auto [opengl_swapchain_saved, opengl_swapchain_our_value, opengl_swapchain_undo_value] = undo_data.get_opengl_swapchain();
if (opengl_swapchain_saved) {
NvAPI_Status status;
NvDRSProfileHandle profile_handle = 0;
status = NvAPI_DRS_GetBaseProfile(session_handle, &profile_handle);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_GetBaseProfile() failed");
return false;
}
NVDRS_SETTING setting = {};
setting.version = NVDRS_SETTING_VER;
status = NvAPI_DRS_GetSetting(session_handle, profile_handle, OGL_CPL_PREFER_DXPRESENT_ID, &setting);
if (status == NVAPI_OK && setting.settingLocation == NVDRS_CURRENT_PROFILE_LOCATION && setting.u32CurrentValue == opengl_swapchain_our_value) {
if (opengl_swapchain_undo_value) {
setting = {};
setting.version = NVDRS_SETTING_VER1;
setting.settingId = OGL_CPL_PREFER_DXPRESENT_ID;
setting.settingType = NVDRS_DWORD_TYPE;
setting.settingLocation = NVDRS_CURRENT_PROFILE_LOCATION;
setting.u32CurrentValue = *opengl_swapchain_undo_value;
status = NvAPI_DRS_SetSetting(session_handle, profile_handle, &setting);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_SetSetting() OGL_CPL_PREFER_DXPRESENT failed");
return false;
}
}
else {
status = NvAPI_DRS_DeleteProfileSetting(session_handle, profile_handle, OGL_CPL_PREFER_DXPRESENT_ID);
if (status != NVAPI_OK && status != NVAPI_SETTING_NOT_FOUND) {
nvapi_error_message(status);
error_message("NvAPI_DRS_DeleteProfileSetting() OGL_CPL_PREFER_DXPRESENT failed");
return false;
}
}
info_message("Restored OGL_CPL_PREFER_DXPRESENT for base profile");
}
else if (status == NVAPI_OK || status == NVAPI_SETTING_NOT_FOUND) {
info_message("OGL_CPL_PREFER_DXPRESENT has been changed from our value in base profile, not restoring");
}
else {
error_message("NvAPI_DRS_GetSetting() OGL_CPL_PREFER_DXPRESENT failed");
return false;
}
}
return true;
}
bool
driver_settings_t::check_and_modify_global_profile(std::optional<undo_data_t> &undo_data) {
if (!session_handle) return false;
undo_data.reset();
NvAPI_Status status;
NvDRSProfileHandle profile_handle = 0;
status = NvAPI_DRS_GetBaseProfile(session_handle, &profile_handle);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_GetBaseProfile() failed");
return false;
}
NVDRS_SETTING setting = {};
setting.version = NVDRS_SETTING_VER;
status = NvAPI_DRS_GetSetting(session_handle, profile_handle, OGL_CPL_PREFER_DXPRESENT_ID, &setting);
// Remember current OpenGL/Vulkan DXGI swapchain setting and change it if needed
if (status == NVAPI_SETTING_NOT_FOUND || (status == NVAPI_OK && setting.u32CurrentValue != OGL_CPL_PREFER_DXPRESENT_PREFER_ENABLED)) {
undo_data = undo_data_t();
if (status == NVAPI_OK) {
undo_data->set_opengl_swapchain(OGL_CPL_PREFER_DXPRESENT_PREFER_ENABLED, setting.u32CurrentValue);
}
else {
undo_data->set_opengl_swapchain(OGL_CPL_PREFER_DXPRESENT_PREFER_ENABLED, std::nullopt);
}
setting = {};
setting.version = NVDRS_SETTING_VER1;
setting.settingId = OGL_CPL_PREFER_DXPRESENT_ID;
setting.settingType = NVDRS_DWORD_TYPE;
setting.settingLocation = NVDRS_CURRENT_PROFILE_LOCATION;
setting.u32CurrentValue = OGL_CPL_PREFER_DXPRESENT_PREFER_ENABLED;
status = NvAPI_DRS_SetSetting(session_handle, profile_handle, &setting);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_SetSetting() OGL_CPL_PREFER_DXPRESENT failed");
return false;
}
info_message("Changed OGL_CPL_PREFER_DXPRESENT to OGL_CPL_PREFER_DXPRESENT_PREFER_ENABLED for base profile");
}
else if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_GetSetting() OGL_CPL_PREFER_DXPRESENT failed");
return false;
}
return true;
}
bool
driver_settings_t::check_and_modify_application_profile(bool &modified) {
if (!session_handle) return false;
modified = false;
NvAPI_Status status;
NvAPI_UnicodeString profile_name = {};
fill_nvapi_string(profile_name, sunshine_application_profile_name);
NvDRSProfileHandle profile_handle = 0;
status = NvAPI_DRS_FindProfileByName(session_handle, profile_name, &profile_handle);
if (status != NVAPI_OK) {
// Create application profile if missing
NVDRS_PROFILE profile = {};
profile.version = NVDRS_PROFILE_VER1;
fill_nvapi_string(profile.profileName, sunshine_application_profile_name);
status = NvAPI_DRS_CreateProfile(session_handle, &profile, &profile_handle);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_CreateProfile() failed");
return false;
}
modified = true;
}
NvAPI_UnicodeString sunshine_path = {};
fill_nvapi_string(sunshine_path, sunshine_application_path);
NVDRS_APPLICATION application = {};
application.version = NVDRS_APPLICATION_VER_V1;
status = NvAPI_DRS_GetApplicationInfo(session_handle, profile_handle, sunshine_path, &application);
if (status != NVAPI_OK) {
// Add application to application profile if missing
application.version = NVDRS_APPLICATION_VER_V1;
application.isPredefined = 0;
fill_nvapi_string(application.appName, sunshine_application_path);
fill_nvapi_string(application.userFriendlyName, sunshine_application_path);
fill_nvapi_string(application.launcher, L"");
status = NvAPI_DRS_CreateApplication(session_handle, profile_handle, &application);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_CreateApplication() failed");
return false;
}
modified = true;
}
NVDRS_SETTING setting = {};
setting.version = NVDRS_SETTING_VER1;
status = NvAPI_DRS_GetSetting(session_handle, profile_handle, PREFERRED_PSTATE_ID, &setting);
if (status != NVAPI_OK ||
setting.settingLocation != NVDRS_CURRENT_PROFILE_LOCATION ||
setting.u32CurrentValue != PREFERRED_PSTATE_PREFER_MAX) {
// Set power setting if needed
setting = {};
setting.version = NVDRS_SETTING_VER1;
setting.settingId = PREFERRED_PSTATE_ID;
setting.settingType = NVDRS_DWORD_TYPE;
setting.settingLocation = NVDRS_CURRENT_PROFILE_LOCATION;
setting.u32CurrentValue = PREFERRED_PSTATE_PREFER_MAX;
status = NvAPI_DRS_SetSetting(session_handle, profile_handle, &setting);
if (status != NVAPI_OK) {
nvapi_error_message(status);
error_message("NvAPI_DRS_SetSetting() PREFERRED_PSTATE failed");
return false;
}
modified = true;
info_message(std::wstring(L"Changed PREFERRED_PSTATE to PREFERRED_PSTATE_PREFER_MAX for ") + sunshine_application_path);
}
return true;
}
} // namespace nvprefs

View File

@ -0,0 +1,36 @@
#pragma once
#include "undo_data.h"
namespace nvprefs {
class driver_settings_t {
public:
~driver_settings_t();
bool
init();
void
destroy();
bool
load_settings();
bool
save_settings();
bool
restore_global_profile_to_undo(const undo_data_t &undo_data);
bool
check_and_modify_global_profile(std::optional<undo_data_t> &undo_data);
bool
check_and_modify_application_profile(bool &modified);
private:
NvDRSSessionHandle session_handle = 0;
};
} // namespace nvprefs

View File

@ -0,0 +1,128 @@
#include "nvprefs_common.h"
#include <nvapi_interface.h>
namespace {
std::map<const char *, void *> interfaces;
HMODULE dll = NULL;
template <typename Func, typename... Args>
NvAPI_Status
call_interface(const char *name, Args... args) {
auto func = (Func *) interfaces[name];
if (!func) {
return interfaces.empty() ? NVAPI_API_NOT_INITIALIZED : NVAPI_NOT_SUPPORTED;
}
return func(args...);
}
} // namespace
#undef NVAPI_INTERFACE
#define NVAPI_INTERFACE NvAPI_Status __cdecl
extern void *
nvapi_QueryInterface(NvU32 id);
NVAPI_INTERFACE
NvAPI_Initialize() {
if (dll) return NVAPI_OK;
#ifdef _WIN64
auto dll_name = "nvapi64.dll";
#else
auto dll_name = "nvapi.dll";
#endif
if ((dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
if (auto query_interface = (decltype(nvapi_QueryInterface) *) GetProcAddress(dll, "nvapi_QueryInterface")) {
for (const auto &item : nvapi_interface_table) {
interfaces[item.func] = query_interface(item.id);
}
return NVAPI_OK;
}
}
NvAPI_Unload();
return NVAPI_LIBRARY_NOT_FOUND;
}
NVAPI_INTERFACE
NvAPI_Unload() {
if (dll) {
interfaces.clear();
FreeLibrary(dll);
dll = NULL;
}
return NVAPI_OK;
}
NVAPI_INTERFACE
NvAPI_GetErrorMessage(NvAPI_Status nr, NvAPI_ShortString szDesc) {
return call_interface<decltype(NvAPI_GetErrorMessage)>("NvAPI_GetErrorMessage", nr, szDesc);
}
// This is only a subset of NvAPI_DRS_* functions, more can be added if needed
NVAPI_INTERFACE
NvAPI_DRS_CreateSession(NvDRSSessionHandle *phSession) {
return call_interface<decltype(NvAPI_DRS_CreateSession)>("NvAPI_DRS_CreateSession", phSession);
}
NVAPI_INTERFACE
NvAPI_DRS_DestroySession(NvDRSSessionHandle hSession) {
return call_interface<decltype(NvAPI_DRS_DestroySession)>("NvAPI_DRS_DestroySession", hSession);
}
NVAPI_INTERFACE
NvAPI_DRS_LoadSettings(NvDRSSessionHandle hSession) {
return call_interface<decltype(NvAPI_DRS_LoadSettings)>("NvAPI_DRS_LoadSettings", hSession);
}
NVAPI_INTERFACE
NvAPI_DRS_SaveSettings(NvDRSSessionHandle hSession) {
return call_interface<decltype(NvAPI_DRS_SaveSettings)>("NvAPI_DRS_SaveSettings", hSession);
}
NVAPI_INTERFACE
NvAPI_DRS_CreateProfile(NvDRSSessionHandle hSession, NVDRS_PROFILE *pProfileInfo, NvDRSProfileHandle *phProfile) {
return call_interface<decltype(NvAPI_DRS_CreateProfile)>("NvAPI_DRS_CreateProfile", hSession, pProfileInfo, phProfile);
}
NVAPI_INTERFACE
NvAPI_DRS_FindProfileByName(NvDRSSessionHandle hSession, NvAPI_UnicodeString profileName, NvDRSProfileHandle *phProfile) {
return call_interface<decltype(NvAPI_DRS_FindProfileByName)>("NvAPI_DRS_FindProfileByName", hSession, profileName, phProfile);
}
NVAPI_INTERFACE
NvAPI_DRS_CreateApplication(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NVDRS_APPLICATION *pApplication) {
return call_interface<decltype(NvAPI_DRS_CreateApplication)>("NvAPI_DRS_CreateApplication", hSession, hProfile, pApplication);
}
NVAPI_INTERFACE
NvAPI_DRS_GetApplicationInfo(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NvAPI_UnicodeString appName, NVDRS_APPLICATION *pApplication) {
return call_interface<decltype(NvAPI_DRS_GetApplicationInfo)>("NvAPI_DRS_GetApplicationInfo", hSession, hProfile, appName, pApplication);
}
NVAPI_INTERFACE
NvAPI_DRS_SetSetting(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NVDRS_SETTING *pSetting) {
return call_interface<decltype(NvAPI_DRS_SetSetting)>("NvAPI_DRS_SetSetting", hSession, hProfile, pSetting);
}
NVAPI_INTERFACE
NvAPI_DRS_GetSetting(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NvU32 settingId, NVDRS_SETTING *pSetting) {
return call_interface<decltype(NvAPI_DRS_GetSetting)>("NvAPI_DRS_GetSetting", hSession, hProfile, settingId, pSetting);
}
NVAPI_INTERFACE
NvAPI_DRS_DeleteProfileSetting(NvDRSSessionHandle hSession, NvDRSProfileHandle hProfile, NvU32 settingId) {
return call_interface<decltype(NvAPI_DRS_DeleteProfileSetting)>("NvAPI_DRS_DeleteProfileSetting", hSession, hProfile, settingId);
}
NVAPI_INTERFACE
NvAPI_DRS_GetBaseProfile(NvDRSSessionHandle hSession, NvDRSProfileHandle *phProfile) {
return call_interface<decltype(NvAPI_DRS_GetBaseProfile)>("NvAPI_DRS_GetBaseProfile", hSession, phProfile);
}

View File

@ -0,0 +1,25 @@
#include "nvprefs_common.h"
namespace nvprefs {
void
info_message(const std::wstring &message) {
BOOST_LOG(info) << "nvprefs: " << message;
}
void
info_message(const std::string &message) {
BOOST_LOG(info) << "nvprefs: " << message;
}
void
error_message(const std::wstring &message) {
BOOST_LOG(error) << "nvprefs: " << message;
}
void
error_message(const std::string &message) {
BOOST_LOG(error) << "nvprefs: " << message;
}
} // namespace nvprefs

View File

@ -0,0 +1,70 @@
#pragma once
// sunshine utility header for generic smart pointers
#include "src/utility.h"
// sunshine boost::log severity levels
#include "src/main.h"
// standard library headers
#include <filesystem>
#include <iostream>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <vector>
// winapi headers
// disable clang-format header reordering
// clang-format off
#include <windows.h>
#include <aclapi.h>
// clang-format on
// nvapi headers
// disable clang-format header reordering
// clang-format off
#include <nvapi.h>
#include <NvApiDriverSettings.h>
// clang-format on
// boost headers
#include <boost/json.hpp>
namespace nvprefs {
struct safe_handle: public util::safe_ptr_v2<void, BOOL, CloseHandle> {
using util::safe_ptr_v2<void, BOOL, CloseHandle>::safe_ptr_v2;
explicit operator bool() const {
auto handle = get();
return handle != NULL && handle != INVALID_HANDLE_VALUE;
}
};
struct safe_hlocal_deleter {
void
operator()(void *p) {
LocalFree(p);
}
};
template <typename T>
using safe_hlocal = util::uniq_ptr<std::remove_pointer_t<T>, safe_hlocal_deleter>;
using safe_sid = util::safe_ptr_v2<void, PVOID, FreeSid>;
void
info_message(const std::wstring &message);
void
info_message(const std::string &message);
void
error_message(const std::wstring &message);
void
error_message(const std::string &message);
} // namespace nvprefs

View File

@ -0,0 +1,225 @@
#include "nvprefs_common.h"
#include "nvprefs_interface.h"
#include "driver_settings.h"
#include "undo_data.h"
#include "undo_file.h"
namespace {
const auto sunshine_program_data_folder = "Sunshine";
const auto nvprefs_undo_file_name = "nvprefs_undo.json";
} // namespace
namespace nvprefs {
struct nvprefs_interface::impl {
bool loaded = false;
driver_settings_t driver_settings;
std::filesystem::path undo_folder_path;
std::filesystem::path undo_file_path;
std::optional<undo_data_t> undo_data;
std::optional<undo_file_t> undo_file;
};
nvprefs_interface::nvprefs_interface():
pimpl(new impl()) {
}
nvprefs_interface::~nvprefs_interface() {
if (owning_undo_file() && load()) {
restore_global_profile();
}
unload();
}
bool
nvprefs_interface::load() {
if (!pimpl->loaded) {
// Check %ProgramData% variable, need it for storing undo file
wchar_t program_data_env[MAX_PATH];
auto get_env_result = GetEnvironmentVariableW(L"ProgramData", program_data_env, MAX_PATH);
if (get_env_result == 0 || get_env_result >= MAX_PATH || !std::filesystem::is_directory(program_data_env)) {
error_message("Missing or malformed %ProgramData% environment variable");
return false;
}
// Prepare undo file path variables
pimpl->undo_folder_path = std::filesystem::path(program_data_env) / sunshine_program_data_folder;
pimpl->undo_file_path = pimpl->undo_folder_path / nvprefs_undo_file_name;
// Dynamically load nvapi library and load driver settings
pimpl->loaded = pimpl->driver_settings.init();
}
return pimpl->loaded;
}
void
nvprefs_interface::unload() {
if (pimpl->loaded) {
// Unload dynamically loaded nvapi library
pimpl->driver_settings.destroy();
pimpl->loaded = false;
}
}
bool
nvprefs_interface::restore_from_and_delete_undo_file_if_exists() {
if (!pimpl->loaded) return false;
// Check for undo file from previous improper termination
bool access_denied = false;
if (auto undo_file = undo_file_t::open_existing_file(pimpl->undo_file_path, access_denied)) {
// Try to restore from the undo file
info_message("Opened undo file from previous improper termination");
if (auto undo_data = undo_file->read_undo_data()) {
if (pimpl->driver_settings.restore_global_profile_to_undo(*undo_data) && pimpl->driver_settings.save_settings()) {
info_message("Restored global profile settings from undo file - deleting the file");
}
else {
error_message("Failed to restore global profile settings from undo file, deleting the file anyway");
}
}
else {
error_message("Coulnd't read undo file, deleting the file anyway");
}
if (!undo_file->delete_file()) {
error_message("Couldn't delete undo file");
return false;
}
}
else if (access_denied) {
error_message("Couldn't open undo file from previous improper termination, or confirm that there's no such file");
return false;
}
return true;
}
bool
nvprefs_interface::modify_application_profile() {
if (!pimpl->loaded) return false;
// Modify and save sunshine.exe application profile settings, if needed
bool modified = false;
if (!pimpl->driver_settings.check_and_modify_application_profile(modified)) {
error_message("Failed to modify application profile settings");
return false;
}
else if (modified) {
if (pimpl->driver_settings.save_settings()) {
info_message("Modified application profile settings");
}
else {
error_message("Couldn't save application profile settings");
return false;
}
}
else {
info_message("No need to modify application profile settings");
}
return true;
}
bool
nvprefs_interface::modify_global_profile() {
if (!pimpl->loaded) return false;
// Modify but not save global profile settings, if needed
std::optional<undo_data_t> undo_data;
if (!pimpl->driver_settings.check_and_modify_global_profile(undo_data)) {
error_message("Couldn't modify global profile settings");
return false;
}
else if (!undo_data) {
info_message("No need to modify global profile settings");
return true;
}
auto make_undo_and_commit = [&]() -> bool {
// Create and lock undo file if it hasn't been done yet
if (!pimpl->undo_file) {
// Prepare Sunshine folder in ProgramData if it doesn't exist
if (!CreateDirectoryW(pimpl->undo_folder_path.c_str(), nullptr) && GetLastError() != ERROR_ALREADY_EXISTS) {
error_message("Couldn't create undo folder");
return false;
}
// Create undo file to handle improper termination of nvprefs.exe
pimpl->undo_file = undo_file_t::create_new_file(pimpl->undo_file_path);
if (!pimpl->undo_file) {
error_message("Couldn't create undo file");
return false;
}
}
assert(undo_data);
if (pimpl->undo_data) {
// Merge undo data if settings has been modified externally since our last modification
pimpl->undo_data->merge(*undo_data);
}
else {
pimpl->undo_data = undo_data;
}
// Write undo data to undo file
if (!pimpl->undo_file->write_undo_data(*pimpl->undo_data)) {
error_message("Couldn't write to undo file - deleting the file");
if (!pimpl->undo_file->delete_file()) {
error_message("Couldn't delete undo file");
}
return false;
}
// Save global profile settings
if (!pimpl->driver_settings.save_settings()) {
error_message("Couldn't save global profile settings");
return false;
}
return true;
};
if (!make_undo_and_commit()) {
// Revert settings modifications
pimpl->driver_settings.load_settings();
return false;
}
return true;
}
bool
nvprefs_interface::owning_undo_file() {
return pimpl->undo_file.has_value();
}
bool
nvprefs_interface::restore_global_profile() {
if (!pimpl->loaded || !pimpl->undo_data || !pimpl->undo_file) return false;
// Restore global profile settings with undo data
if (pimpl->driver_settings.restore_global_profile_to_undo(*pimpl->undo_data) &&
pimpl->driver_settings.save_settings()) {
// Global profile settings sucessfully restored, can delete undo file
if (!pimpl->undo_file->delete_file()) {
error_message("Couldn't delete undo file");
return false;
}
pimpl->undo_data = std::nullopt;
pimpl->undo_file = std::nullopt;
}
else {
error_message("Couldn't restore global profile settings");
return false;
}
return true;
}
} // namespace nvprefs

View File

@ -0,0 +1,36 @@
#pragma once
namespace nvprefs {
class nvprefs_interface {
public:
nvprefs_interface();
~nvprefs_interface();
bool
load();
void
unload();
bool
restore_from_and_delete_undo_file_if_exists();
bool
modify_application_profile();
bool
modify_global_profile();
bool
owning_undo_file();
bool
restore_global_profile();
private:
struct impl;
std::unique_ptr<impl> pimpl;
};
} // namespace nvprefs

View File

@ -0,0 +1,71 @@
#include "nvprefs_common.h"
#include "undo_data.h"
namespace {
const auto opengl_swapchain_our_value_key = "/opengl_swapchain/our_value";
const auto opengl_swapchain_undo_value_key = "/opengl_swapchain/undo_value";
} // namespace
namespace nvprefs {
void
undo_data_t::set_opengl_swapchain(uint32_t our_value, std::optional<uint32_t> undo_value) {
data.set_at_pointer(opengl_swapchain_our_value_key, our_value);
if (undo_value) {
data.set_at_pointer(opengl_swapchain_undo_value_key, *undo_value);
}
else {
data.set_at_pointer(opengl_swapchain_undo_value_key, nullptr);
}
}
std::tuple<bool, uint32_t, std::optional<uint32_t>>
undo_data_t::get_opengl_swapchain() const {
auto get_value = [this](const auto &key) -> std::tuple<bool, std::optional<uint32_t>> {
try {
auto value = data.at_pointer(key);
if (value.is_null()) {
return { true, std::nullopt };
}
else if (value.is_number()) {
return { true, value.template to_number<uint32_t>() };
}
}
catch (...) {
}
error_message(std::string("Couldn't find ") + key + " element");
return { false, std::nullopt };
};
auto [our_value_present, our_value] = get_value(opengl_swapchain_our_value_key);
auto [undo_value_present, undo_value] = get_value(opengl_swapchain_undo_value_key);
if (!our_value_present || !undo_value_present || !our_value) {
return { false, 0, std::nullopt };
}
return { true, *our_value, undo_value };
}
std::string
undo_data_t::write() const {
return boost::json::serialize(data);
}
void
undo_data_t::read(const std::vector<char> &buffer) {
data = boost::json::parse(std::string_view(buffer.data(), buffer.size()));
}
void
undo_data_t::merge(const undo_data_t &newer_data) {
auto [opengl_swapchain_saved, opengl_swapchain_our_value, opengl_swapchain_undo_value] = newer_data.get_opengl_swapchain();
if (opengl_swapchain_saved) {
set_opengl_swapchain(opengl_swapchain_our_value, opengl_swapchain_undo_value);
}
}
} // namespace nvprefs

View File

@ -0,0 +1,32 @@
#pragma once
namespace nvprefs {
class undo_data_t {
public:
void
set_opengl_swapchain(uint32_t our_value, std::optional<uint32_t> undo_value);
std::tuple<bool, uint32_t, std::optional<uint32_t>>
get_opengl_swapchain() const;
void
write(std::ostream &stream) const;
std::string
write() const;
void
read(std::istream &stream);
void
read(const std::vector<char> &buffer);
void
merge(const undo_data_t &newer_data);
private:
boost::json::value data;
};
} // namespace nvprefs

View File

@ -0,0 +1,154 @@
#include "nvprefs_common.h"
#include "undo_file.h"
namespace {
using namespace nvprefs;
DWORD
relax_permissions(HANDLE file_handle) {
PACL old_dacl = nullptr;
safe_hlocal<PSECURITY_DESCRIPTOR> sd;
DWORD status = GetSecurityInfo(file_handle, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &old_dacl, nullptr, &sd);
if (status != ERROR_SUCCESS) return status;
safe_sid users_sid;
SID_IDENTIFIER_AUTHORITY nt_authorithy = SECURITY_NT_AUTHORITY;
if (!AllocateAndInitializeSid(&nt_authorithy, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_USERS, 0, 0, 0, 0, 0, 0, &users_sid)) {
return GetLastError();
}
EXPLICIT_ACCESS ea = {};
ea.grfAccessPermissions = GENERIC_READ | GENERIC_WRITE | DELETE;
ea.grfAccessMode = GRANT_ACCESS;
ea.grfInheritance = NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea.Trustee.ptstrName = (LPTSTR) users_sid.get();
safe_hlocal<PACL> new_dacl;
status = SetEntriesInAcl(1, &ea, old_dacl, &new_dacl);
if (status != ERROR_SUCCESS) return status;
status = SetSecurityInfo(file_handle, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, new_dacl.get(), nullptr);
if (status != ERROR_SUCCESS) return status;
return 0;
}
} // namespace
namespace nvprefs {
std::optional<undo_file_t>
undo_file_t::open_existing_file(std::filesystem::path file_path, bool &access_denied) {
undo_file_t file;
file.file_handle.reset(CreateFileW(file_path.c_str(), GENERIC_READ | DELETE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL));
if (file.file_handle) {
access_denied = false;
return file;
}
else {
auto last_error = GetLastError();
access_denied = (last_error != ERROR_FILE_NOT_FOUND && last_error != ERROR_PATH_NOT_FOUND);
return std::nullopt;
}
}
std::optional<undo_file_t>
undo_file_t::create_new_file(std::filesystem::path file_path) {
undo_file_t file;
file.file_handle.reset(CreateFileW(file_path.c_str(), GENERIC_WRITE | STANDARD_RIGHTS_ALL, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL));
if (file.file_handle) {
// give GENERIC_READ, GENERIC_WRITE and DELETE permissions to Users group
if (relax_permissions(file.file_handle.get()) != 0) {
error_message("Failed to relax permissions on undo file");
}
return file;
}
else {
return std::nullopt;
}
}
bool
undo_file_t::delete_file() {
if (!file_handle) return false;
FILE_DISPOSITION_INFO delete_file_info = { TRUE };
if (SetFileInformationByHandle(file_handle.get(), FileDispositionInfo, &delete_file_info, sizeof(delete_file_info))) {
file_handle.reset();
return true;
}
else {
return false;
}
}
bool
undo_file_t::write_undo_data(const undo_data_t &undo_data) {
if (!file_handle) return false;
std::string buffer;
try {
buffer = undo_data.write();
}
catch (...) {
error_message("Couldn't serialize undo data");
return false;
}
if (!SetFilePointerEx(file_handle.get(), {}, nullptr, FILE_BEGIN) || !SetEndOfFile(file_handle.get())) {
error_message("Couldn't clear undo file");
return false;
}
DWORD bytes_written = 0;
if (!WriteFile(file_handle.get(), buffer.data(), buffer.size(), &bytes_written, nullptr) || bytes_written != buffer.size()) {
error_message("Couldn't write undo file");
return false;
}
if (!FlushFileBuffers(file_handle.get())) {
error_message("Failed to flush undo file");
}
return true;
}
std::optional<undo_data_t>
undo_file_t::read_undo_data() {
if (!file_handle) return std::nullopt;
LARGE_INTEGER file_size;
if (!GetFileSizeEx(file_handle.get(), &file_size)) {
error_message("Couldn't get undo file size");
return std::nullopt;
}
if ((size_t) file_size.QuadPart > 1024) {
error_message("Undo file size is unexpectedly large, aborting");
return std::nullopt;
}
std::vector<char> buffer(file_size.QuadPart);
DWORD bytes_read = 0;
if (!ReadFile(file_handle.get(), buffer.data(), buffer.size(), &bytes_read, nullptr) || bytes_read != buffer.size()) {
error_message("Couldn't read undo file");
return std::nullopt;
}
undo_data_t undo_data;
try {
undo_data.read(buffer);
}
catch (...) {
error_message("Couldn't parse undo file");
return std::nullopt;
}
return undo_data;
}
} // namespace nvprefs

View File

@ -0,0 +1,29 @@
#pragma once
#include "undo_data.h"
namespace nvprefs {
class undo_file_t {
public:
static std::optional<undo_file_t>
open_existing_file(std::filesystem::path file_path, bool &access_denied);
static std::optional<undo_file_t>
create_new_file(std::filesystem::path file_path);
bool
delete_file();
bool
write_undo_data(const undo_data_t &undo_data);
std::optional<undo_data_t>
read_undo_data();
private:
undo_file_t() = default;
safe_handle file_handle;
};
} // namespace nvprefs