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:
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user