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