Launcher: kill whole process tree on stop; fix Win10 install.bat icacls

Two field issues from the live rollout, both launcher-side.

1) Console "Stop" did nothing. Every Tesla game runs under a supervisor
   that stays alive and respawns the game (Firestorm -> launcher.exe,
   Red Planet -> a looping .bat under cmd.exe, tesla410revival ->
   pod-launch.exe -> dosbox). net40's Process.Kill() terminates only the
   tracked supervisor PID, so the game survived (and the supervisor/loop
   relaunched it). New KillProcessTree uses `taskkill /PID <pid> /T /F`
   (whole tree; XP Pro + Win10/11), Process.Kill() as fallback. All three
   kill paths (KillApp/KillAllOfType/KillAllApps) now untrack under the
   lock and tree-kill outside it, so the RPC lock isn't held across
   taskkill and our auto-restart watcher won't relaunch. Proven against a
   real supervisor->child: old Kill orphaned the child; tree-kill takes both.

2) install.bat threw "(CI)M was unexpected at this time" at [1/7] on
   Windows 10. The icacls `/grant *S-1-5-32-545:(OI)(CI)M` sat inside an
   `else ( ... )` block; cmd read the literal ) in (OI)(CI) as the block
   end. XP was fine (its branch uses cacls, no parens). Quote the grant
   token at all three icacls sites; move the explanatory notes above the
   blocks (a stray ) even in a rem inside ( ) is the same trap). Verified
   on Win11: as-shipped reproduces the error, fixed form parses (rc=0) and
   applies the identical ACE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-17 20:38:36 -05:00
co-authored by Claude Fable 5
parent 85595b8c52
commit 6973e7d60c
2 changed files with 68 additions and 10 deletions
+60 -7
View File
@@ -502,6 +502,11 @@ namespace Tesla.Launcher
private void CmdKillApp(Guid launchKey, int? pid)
{
var key = FindKeyString(launchKey);
var victims = new List<Process>();
// Untrack under the lock, terminate outside it: KillProcessTree spawns
// taskkill and waits, which must not block the RPC lock. Untracking
// first also stops our own auto-restart watcher from relaunching (its
// stillTracked check fails once the key is gone).
lock (_processLock)
{
List<Process> procs;
@@ -513,38 +518,86 @@ namespace Tesla.Launcher
foreach (var p in toKill)
{
try { if (!p.HasExited) p.Kill(); } catch { }
victims.Add(p);
procs.Remove(p);
}
if (procs.Count == 0) _runningProcesses.Remove(key);
}
KillProcessTrees(victims);
}
private void CmdKillAllOfType(Guid launchKey)
{
var key = FindKeyString(launchKey);
List<Process> victims;
lock (_processLock)
{
List<Process> procs;
if (key == null || !_runningProcesses.TryGetValue(key, out procs)) return;
foreach (var p in procs)
try { if (!p.HasExited) p.Kill(); } catch { }
if (key == null || !_runningProcesses.TryGetValue(key, out victims)) return;
_runningProcesses.Remove(key);
}
KillProcessTrees(victims);
}
private void CmdKillAllApps()
{
var victims = new List<Process>();
lock (_processLock)
{
foreach (var kvp in _runningProcesses)
foreach (var p in kvp.Value)
try { if (!p.HasExited) p.Kill(); } catch { }
victims.AddRange(kvp.Value);
_runningProcesses.Clear();
}
KillProcessTrees(victims);
SetTrayStatus("All apps stopped");
}
private static void KillProcessTrees(IEnumerable<Process> procs)
{
if (procs == null) return;
foreach (var p in procs) KillProcessTree(p);
}
// Every Tesla game runs under a supervisor that stays alive and re-spawns
// the game (Firestorm -> launcher.exe, Red Planet -> a looping .bat under
// cmd.exe, tesla410revival -> pod-launch.exe -> dosbox). net40's
// Process.Kill() terminates ONLY the tracked supervisor PID, so the game
// survives (and the supervisor/loop relaunches it). taskkill /T walks the
// parent-PID tree and terminates the supervisor AND its children in one
// shot; it ships on XP Professional and Win10/11. Process.Kill() remains
// the fallback if taskkill is somehow unavailable.
private static void KillProcessTree(Process p)
{
if (p == null) return;
int pid;
try
{
if (p.HasExited) return;
pid = p.Id;
}
catch { return; }
try
{
var psi = new ProcessStartInfo("taskkill", "/PID " + pid + " /T /F")
{
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
};
using (var tk = Process.Start(psi))
{
if (tk != null)
{
tk.WaitForExit(10000);
return;
}
}
}
catch { /* taskkill missing/failed — fall back to single-process kill */ }
try { if (!p.HasExited) p.Kill(); } catch { }
}
private void CmdShutdown(bool restart)
{
CmdKillAllApps();
+8 -3
View File
@@ -142,12 +142,15 @@ if not exist "C:\Games" mkdir "C:\Games"
:: Grant Users modify access to the data and games directories so the launcher
:: can write files (LaunchApps.xml, session key, game installs) from any account.
:: The icacls grant token MUST stay quoted: its (OI)(CI) inheritance parens would
:: otherwise be read as the end of the if-block -- "(CI)M was unexpected at this
:: time" on Win10. Keep this note ABOVE the block; a stray ) inside ( ) breaks it.
if "%ISXP%"=="1" (
cacls "%DATA_DIR%" /T /E /G Users:C >nul 2>&1
cacls "C:\Games" /T /E /G Users:C >nul 2>&1
) else (
icacls "%DATA_DIR%" /grant *S-1-5-32-545:(OI)(CI)M /T >nul 2>&1
icacls "C:\Games" /grant *S-1-5-32-545:(OI)(CI)M /T >nul 2>&1
icacls "%DATA_DIR%" /grant "*S-1-5-32-545:(OI)(CI)M" /T >nul 2>&1
icacls "C:\Games" /grant "*S-1-5-32-545:(OI)(CI)M" /T >nul 2>&1
)
echo %INSTALL_DIR%
echo %DATA_DIR% (Users: modify access)
@@ -203,12 +206,14 @@ if "%ISXP%"=="0" (
)
:: Create and share game-data folders (closed network - open to Everyone).
:: The icacls grant token stays quoted so its (OI)(CI) parens are not read as the
:: end of the if-block. Keep this note ABOVE the block, not inside the ( ).
echo Creating network shares...
if not exist "C:\mw4files" mkdir "C:\mw4files"
if "%ISXP%"=="1" (
cacls "C:\mw4files" /T /E /G Everyone:C >nul 2>&1
) else (
icacls "C:\mw4files" /grant *S-1-1-0:(OI)(CI)M /T >nul 2>&1
icacls "C:\mw4files" /grant "*S-1-1-0:(OI)(CI)M" /T >nul 2>&1
)
net share mw4files /delete >nul 2>&1
net share mw4files=C:\mw4files /grant:Everyone,FULL >nul 2>&1