diff --git a/src/spice2x/build/manifest.manifest b/src/spice2x/build/manifest.manifest index 1810dc4..f4d072b 100644 --- a/src/spice2x/build/manifest.manifest +++ b/src/spice2x/build/manifest.manifest @@ -23,7 +23,7 @@ - + diff --git a/src/spice2x/cfg/manifest.manifest b/src/spice2x/cfg/manifest.manifest index 813e32b..528088f 100644 --- a/src/spice2x/cfg/manifest.manifest +++ b/src/spice2x/cfg/manifest.manifest @@ -20,10 +20,10 @@ - - - - - + + + + + \ No newline at end of file diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index a61876a..67b67a3 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include "api/modules/capture.h" @@ -301,6 +302,26 @@ int main_implementation(int argc, char *argv[]) { } auto &options = *options_ptr; +#if !SPICE_XP + + { + // skip elevation only if AutoElevate is explicitly set to "user" + const bool skip_elevation = options[launcher::Options::AutoElevate].is_active() && + options[launcher::Options::AutoElevate].value_text() == "user"; + if (!skip_elevation && !sysutils::is_running_as_admin()) { + log_info("launcher", "relaunching with administrator privileges"); + if (sysutils::relaunch_as_admin()) { + exit(0); + } else { + // elevation failed or was denied by the user + log_fatal("launcher", "failed to launch with administrator privileges"); + exit(1); + } + } + } + +#endif // !SPICE_XP + // check options // TODO: get rid of some booleans here and make use of the options directly if (options[launcher::Options::OpenConfigurator].value_bool()) { diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index e5014ec..9ed847b 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -2812,6 +2812,22 @@ static const std::vector OPTION_DEFINITIONS = { .type = OptionType::Bool, .category = "Network (Development)", }, + { + // AutoElevate + .title = "Run as", + .name = "runas", + .desc = "Controls whether spice will automatically re-launch with administrator privileges at startup.\n\n" + "admin (default): automatically re-launch with administrator privileges, " + "needed for most games to function properly\n\n" + "user: skip elevation, run with current user privileges; game may fail to launch, " + "crash, or critical features may silently fail; use at your own risk.", + .type = OptionType::Enum, + .category = "Development", + .elements = { + {"admin", ""}, + {"user", ""}, + }, + }, }; const std::vector &launcher::get_categories(Options::OptionsCategory category) { diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index c0fec7d..dfddf57 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -283,6 +283,7 @@ namespace launcher { OtocaCamHook, DisableHighResTimer, EnableICMPHook, + AutoElevate, }; enum class OptionsCategory { diff --git a/src/spice2x/util/sysutils.cpp b/src/spice2x/util/sysutils.cpp index 0ceb621..e053d45 100644 --- a/src/spice2x/util/sysutils.cpp +++ b/src/spice2x/util/sysutils.cpp @@ -10,6 +10,9 @@ #include #undef WIN32_NO_STATUS +#include +#include + #include "hooks/graphics/graphics.h" #include "avs/game.h" #include "util/detour.h" @@ -585,4 +588,56 @@ namespace sysutils { (void*)EnumDisplayDevicesA_hook, (void**)&EnumDisplayDevicesA_orig); } + +#if !SPICE_XP + + bool is_running_as_admin() { + HANDLE token_handle = nullptr; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle)) { + return false; + } + + TOKEN_ELEVATION elevation = {}; + DWORD size = sizeof(elevation); + const BOOL ok = GetTokenInformation( + token_handle, TokenElevation, &elevation, size, &size); + CloseHandle(token_handle); + + if (!ok) { + return false; + } + return elevation.TokenIsElevated != 0; + } + + bool relaunch_as_admin() { + // get the current executable path + wchar_t executable_path[MAX_PATH]; + if (!GetModuleFileNameW(nullptr, executable_path, MAX_PATH)) { + return false; + } + + // get the current working directory to pass to the elevated process + wchar_t working_dir[MAX_PATH]; + if (!GetCurrentDirectoryW(MAX_PATH, working_dir)) { + return false; + } + + // skip past argv[0] in the original command line + const wchar_t *args = PathGetArgsW(GetCommandLineW()); + + // use ShellExecuteW with "runas" verb to elevate + HINSTANCE result = ShellExecuteW( + nullptr, + L"runas", + executable_path, + args, + working_dir, + SW_SHOW + ); + + // ShellExecute returns > 32 on success + return (intptr_t)result > 32; + } + +#endif // !SPICE_XP } diff --git a/src/spice2x/util/sysutils.h b/src/spice2x/util/sysutils.h index 343504f..44d32fa 100644 --- a/src/spice2x/util/sysutils.h +++ b/src/spice2x/util/sysutils.h @@ -21,5 +21,12 @@ namespace sysutils { const std::vector &enumerate_monitors(); extern std::string SECOND_MONITOR_OVERRIDE; - void hook_EnumDisplayDevicesA(); -} \ No newline at end of file + void hook_EnumDisplayDevicesA(); + +#if !SPICE_XP + + bool is_running_as_admin(); + bool relaunch_as_admin(); + +#endif // !SPICE_XP +}