windows: verify DEP mitigation and launcher creation

This commit is contained in:
Dusk-92
2026-08-27 15:44:19 +02:00
parent 8b5e0710f8
commit 5cdb9a34ba
+217 -35
View File
@@ -1433,21 +1433,138 @@ class WowSetupTool:
)
def apply_process_mitigations(self):
"""Runs the Set-ProcessMitigation PowerShell command with a UAC prompt if needed."""
if not self.vt_dep_fix.get():
return
ps_cmd = "Set-ProcessMitigation -Name WoW_Modernized.exe -Disable DEP, EmulateAtlThunks"
# Start-Process with '-Verb RunAs' triggers the Windows Administrator UAC prompt automatically
full_cmd = f"Start-Process powershell -WindowStyle Hidden -Verb RunAs -ArgumentList \"-Command {ps_cmd}\""
def _dep_marker_path(self, target_dir):
return os.path.join(
target_dir,
".modernization_tool",
"dep_override.json",
)
def _previous_settings_used_dep_override(self, target_dir):
"""Use the last successful settings file as migration/ownership evidence."""
settings_path = self._settings_path(target_dir)
if not os.path.isfile(settings_path):
return False
try:
# Execute the elevation request
subprocess.run(["powershell", "-Command", full_cmd], creationflags=0x08000000)
except Exception:
pass # Fail silently if the user clicks "No" on the Administrator prompt
with open(settings_path, "r", encoding="utf-8") as handle:
saved = json.load(handle)
tweaks = saved.get("vanilla_tweaks", {})
return isinstance(tweaks, dict) and tweaks.get("dep_fix") is True
except (OSError, json.JSONDecodeError, ValueError, TypeError):
return False
def _dep_override_owned_by_tool(self, target_dir):
return (
os.path.isfile(self._dep_marker_path(target_dir))
or self._previous_settings_used_dep_override(target_dir)
)
def _run_elevated_powershell(self, target_dir, script_text, action_name):
"""Run one small PowerShell script elevated and verify its exit code."""
support_dir = os.path.join(target_dir, ".modernization_tool")
os.makedirs(support_dir, exist_ok=True)
script_path = os.path.join(support_dir, "process_mitigation.ps1")
with open(script_path, "w", encoding="utf-8", newline="\n") as handle:
handle.write("$ErrorActionPreference = 'Stop'\n")
handle.write(script_text.rstrip() + "\n")
handle.write("exit 0\n")
escaped_script = script_path.replace("'", "''")
launcher = (
"$ErrorActionPreference='Stop'; "
"try { "
"$p = Start-Process -FilePath 'powershell.exe' -Verb RunAs "
"-Wait -PassThru -ArgumentList @("
"'-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass',"
f"'-File','{escaped_script}'"
"); "
"exit $p.ExitCode "
"} catch { Write-Error $_; exit 1 }"
)
try:
result = subprocess.run(
[
"powershell",
"-NoProfile",
"-NonInteractive",
"-Command",
launcher,
],
creationflags=0x08000000,
capture_output=True,
text=True,
)
except FileNotFoundError as exc:
raise RuntimeError(
f"{action_name} could not run because Windows PowerShell was not found."
) from exc
finally:
try:
os.remove(script_path)
except OSError:
pass
if result.returncode != 0:
details = (result.stderr or result.stdout or "").strip()
if len(details) > 800:
details = details[-800:]
suffix = f"\n\nDetails: {details}" if details else ""
raise RuntimeError(
f"{action_name} was not completed. The Administrator/UAC prompt may "
f"have been declined, or Windows rejected the mitigation change.{suffix}"
)
def apply_process_mitigations(self, target_dir):
"""Apply or restore the per-app DEP override with verified UAC completion."""
marker_path = self._dep_marker_path(target_dir)
if self.vt_dep_fix.get():
self._run_elevated_powershell(
target_dir,
"Set-ProcessMitigation -Name 'WoW_Modernized.exe' "
"-Disable DEP, EmulateAtlThunks",
"Disabling DEP for WoW_Modernized.exe",
)
# Keep explicit ownership evidence so disabling this option later
# restores only a setting that Modernization Tool applied.
try:
os.makedirs(os.path.dirname(marker_path), exist_ok=True)
temp_marker = marker_path + ".new"
with open(temp_marker, "w", encoding="utf-8") as handle:
json.dump(
{
"target": "WoW_Modernized.exe",
"disabled": ["DEP", "EmulateAtlThunks"],
},
handle,
indent=2,
)
os.replace(temp_marker, marker_path)
except OSError:
# settings.json is also ownership evidence after a successful
# Apply, so a marker write failure must not invalidate DEP.
pass
return
if not self._dep_override_owned_by_tool(target_dir):
return
# Microsoft documents -Remove together with -Disable for restoring an
# app-specific mitigation override back to the system default.
self._run_elevated_powershell(
target_dir,
"Set-ProcessMitigation -Name 'WoW_Modernized.exe' "
"-Remove -Disable DEP, EmulateAtlThunks",
"Restoring the system DEP defaults for WoW_Modernized.exe",
)
try:
os.remove(marker_path)
except OSError:
pass
def _set_install_busy(self, busy):
self._install_in_progress = bool(busy)
@@ -1498,7 +1615,7 @@ class WowSetupTool:
self.run_vanilla_tweaks(target_dir)
self.configure_script_memory(target_dir)
self.configure_wdb_cache(target_dir)
self.apply_process_mitigations()
self.apply_process_mitigations(target_dir)
# Generate the seamless launcher shortcut.
self.create_launcher_shortcut(target_dir)
@@ -1575,44 +1692,109 @@ class WowSetupTool:
pass
def create_launcher_shortcut(self, target_dir):
"""Automates creating the VanillaFixes shortcut targeting WoW_Modernized.exe"""
"""Create the launcher transactionally and verify cscript succeeded."""
shortcut_path = os.path.join(target_dir, "Play Modernized WoW.lnk")
staged_shortcut = shortcut_path + ".modernization-new.lnk"
vanilla_fixes_exe = os.path.join(target_dir, "VanillaFixes.exe")
# Keep support assets out of the game root.
source_icon = os.path.join(get_base_path(), "PurpleWowLogo.ico")
modernized_exe = os.path.join(target_dir, "WoW_Modernized.exe")
if not os.path.isfile(vanilla_fixes_exe):
raise RuntimeError(
"VanillaFixes.exe is missing, so the launcher shortcut cannot be created."
)
if not os.path.isfile(modernized_exe):
raise RuntimeError(
"WoW_Modernized.exe is missing, so the launcher shortcut cannot be created."
)
support_dir = os.path.join(target_dir, ".modernization_tool")
os.makedirs(support_dir, exist_ok=True)
source_icon = os.path.join(get_base_path(), "PurpleWowLogo.ico")
target_icon = os.path.join(support_dir, "PurpleWowLogo.ico")
icon_vbs_line = ""
if os.path.exists(source_icon):
if os.path.isfile(source_icon):
try:
os.makedirs(support_dir, exist_ok=True)
shutil.copy2(source_icon, target_icon)
icon_vbs_line = f'oLink.IconLocation = "{target_icon}, 0"'
remote_packages._atomic_replace_file(source_icon, target_icon)
escaped_icon = (target_icon + ", 0").replace('"', '""')
icon_vbs_line = f'oLink.IconLocation = "{escaped_icon}"'
except Exception:
pass # The shortcut can still be created without a custom icon.
# A custom icon is cosmetic; shortcut creation may continue.
icon_vbs_line = ""
def vbs_escape(value):
return str(value).replace('"', '""')
vbs_script = f"""
Set oWS = WScript.CreateObject("WScript.Shell")
sLinkFile = "{shortcut_path}"
sLinkFile = "{vbs_escape(staged_shortcut)}"
Set oLink = oWS.CreateShortcut(sLinkFile)
oLink.TargetPath = "{vanilla_fixes_exe}"
oLink.TargetPath = "{vbs_escape(vanilla_fixes_exe)}"
oLink.Arguments = "WoW_Modernized.exe"
oLink.WorkingDirectory = "{target_dir}"
oLink.WorkingDirectory = "{vbs_escape(target_dir)}"
oLink.Description = "Launch Vanilla WoW with VanillaFixes and Tweaks"
{icon_vbs_line}
oLink.Save
"""
vbs_path = os.path.join(target_dir, "create_shortcut.vbs")
with open(vbs_path, "w") as f:
f.write(vbs_script)
vbs_path = os.path.join(support_dir, "create_shortcut.vbs")
if os.path.exists(staged_shortcut):
try:
os.remove(staged_shortcut)
except OSError as exc:
raise RuntimeError(
f"Could not prepare the launcher shortcut update: {exc}"
) from exc
try:
subprocess.run(["cscript", "//nologo", vbs_path], creationflags=0x08000000)
with open(vbs_path, "w", encoding="utf-8", newline="\r\n") as handle:
handle.write(vbs_script)
try:
result = subprocess.run(
["cscript", "//nologo", vbs_path],
creationflags=0x08000000,
capture_output=True,
text=True,
)
except FileNotFoundError as exc:
raise RuntimeError(
"Windows Script Host (cscript.exe) was not found, so the "
"launcher shortcut could not be created."
) from exc
if result.returncode != 0:
details = (result.stderr or result.stdout or "").strip()
if len(details) > 800:
details = details[-800:]
suffix = f"\n\nDetails: {details}" if details else ""
raise RuntimeError(
"Windows Script Host failed to create the launcher shortcut."
+ suffix
)
if (
not os.path.isfile(staged_shortcut)
or os.path.getsize(staged_shortcut) <= 0
):
raise RuntimeError(
"Windows reported successful shortcut creation, but the new "
"Play Modernized WoW shortcut was not produced."
)
# Only replace the user's current shortcut after the new one has
# been created and verified, so a cscript failure leaves the old
# shortcut intact.
os.replace(staged_shortcut, shortcut_path)
finally:
if os.path.exists(vbs_path):
os.remove(vbs_path)
for temp_path in (vbs_path, staged_shortcut):
if os.path.exists(temp_path):
try:
os.remove(temp_path)
except OSError:
pass
def copy_base_files(self, target):
payload_dir = os.path.join(get_base_path(), "Payload")