public void ProcessLauncher(Settings setHnd, IniFile iniHnd) {// pass our Settings and IniFile contexts into this workhorse routine String launcherName = Path.GetFileNameWithoutExtension(setHnd.LauncherPath); String gameName = Path.GetFileNameWithoutExtension(setHnd.GamePath); String launcherMode = setHnd.LauncherMode; Process launcherProc = new Process(); Process gameProc = new Process(); TrayIconUtil trayUtil = new TrayIconUtil(); // save detected process types for both our launcher and game int launcherType = -1; int gameType = -1; // save our monitoring path for later String monitorPath = Settings.ValidatePath(setHnd.MonitorPath) ? setHnd.MonitorPath : String.Empty; String monitorName = Path.GetFileNameWithoutExtension(monitorPath); String _launchType = (monitorPath.Length > 0 ? "monitor" : "game"); // save PIDs that we find int launcherPID = 0; int gamePID = 0; /* * Launcher Detection: * * 1) If LauncherPath not set skip to Step 9 * 2) If SkipLauncher is set skip to Step 9 * 3) Check if LauncherPath is actively running - relaunch via Steam->OSOL * 4) Execute pre-launcher delegate * 5) Execute launcher (using ShellExecute) - use LauncherURI if set (not ShellExecute) * 6) Hand off to GetProcessTreeHandle() for detection * a) GetProcessTreeHandle() loops on timeout until valid process is detected * b) ValidateProcessTree() attempts to validate Process and PID returns * c) Returns Process with correct PID and Process handle * 7) Validate detection return (pass back launcher type case number) * 8) Perform post-launcher behaviors * 9) Continue to game */ #region LauncherDetection // only use validated launcher if CommandlineProxy is not enabled, Launcher is not forced, and we have no DetectedCommandline if (!setHnd.SkipLauncher & Settings.ValidatePath(setHnd.LauncherPath) & (setHnd.ForceLauncher || !setHnd.CommandlineProxy || setHnd.DetectedCommandline.Length == 0)) { // check for running instance of launcher (relaunch if required) if (Program.IsRunning(launcherName) && setHnd.ReLaunch) {// if the launcher is running before the game kill it so we can run it through Steam Program.Logger("OSOL", "Found previous instance of launcher by name, killing and relaunching..."); Program.KillProcTreeByName(launcherName); Thread.Sleep(setHnd.ProxyTimeout * 1000); // pause a moment for the launcher to close } // ask a non-async delegate to run a process before the launcher Program.ExecuteExternalElevated(setHnd, setHnd.PreLaunchExec, setHnd.PreLaunchExecArgs); if (Program.StringEquals(launcherMode, "URI") && !String.IsNullOrEmpty(setHnd.LauncherURI)) {// use URI launching as a mutually exclusive alternative to "Normal" launch mode (calls launcher->game) gameProc.StartInfo.UseShellExecute = true; gameProc.StartInfo.FileName = setHnd.LauncherURI; gameProc.StartInfo.Arguments = setHnd.GameArgs; Program.Logger("OSOL", String.Format("Launching URI: {0} {1}", setHnd.LauncherURI, setHnd.GameArgs)); LaunchProcess(gameProc); } else { launcherProc.StartInfo.UseShellExecute = true; launcherProc.StartInfo.FileName = setHnd.LauncherPath; launcherProc.StartInfo.WorkingDirectory = Directory.GetParent(setHnd.LauncherPath).ToString(); launcherProc.StartInfo.Arguments = setHnd.LauncherArgs; Program.Logger("OSOL", String.Format("Attempting to start the launcher: {0}", setHnd.LauncherPath)); LaunchProcess(launcherProc); } // loop until we have a valid process handle // pass a ref out of GetProcessTreeHandle() with our detected process type launcherProc = GetProcessTreeHandle(setHnd, launcherName, ref launcherType); launcherPID = launcherProc != null ? launcherProc.Id : 0; if (launcherPID > 0) { // do some waiting based on user tuneables to avoid BPM weirdness Program.Logger("OSOL", String.Format("Waiting {0}s for launcher process to load...", setHnd.PreGameLauncherWaitTime)); Thread.Sleep(setHnd.PreGameLauncherWaitTime * 1000); if (launcherType > -1) {// we can only send window messages if we have a window handle Program.BringToFront(launcherProc.MainWindowHandle); if (setHnd.MinimizeLauncher) { Program.MinimizeWindow(launcherProc.MainWindowHandle); } } } } #endregion /* * Game Process Detection: * * 1) Only launch GamePath if we're in "Normal" launch mode (pre-validated) * 2) Execute GamePath (or use Launcher if we have an exclusive case) * 3) Hand off to GetProcessTreeHandle() for detection * a) GetProcessTreeHandle() loops on timeout until valid process is detected * b) ValidateProcessTree() attempts to validate Process and PID returns * c) Returns Process with correct PID and Process handle * 4) If we're using CommandlineProxy attempt to detect the target process cmdline * a) If we've got a cmdline relaunch GamePath with it * 5) Validate game process detection return * 6) Do post-game-detection steps * 7) Spin for Game process until it exits */ #region GameDetection if (Program.StringEquals(launcherMode, "Normal")) {// we're not in URI or LauncherOnly modes - this is the default gameProc.StartInfo.UseShellExecute = true; gameProc.StartInfo.FileName = setHnd.GamePath; gameProc.StartInfo.WorkingDirectory = Directory.GetParent(setHnd.GamePath).ToString(); if (launcherType == 1) { // we've detected Battle.net Launcher so let's ask the launcher politely to start the game gameProc.StartInfo.FileName = setHnd.LauncherPath; // use the launcher - look for the game below in GetProcessTreeHandle() gameProc.StartInfo.Arguments = setHnd.LauncherArgs; // these contain the game launch command Program.Logger("OSOL", String.Format("Detected Battle.net launcher, calling game via: {0} {1}", setHnd.LauncherPath, setHnd.LauncherArgs)); LaunchProcess(gameProc); } else if (setHnd.CommandlineProxy && setHnd.DetectedCommandline.Length > 0) {// avoid executing GamePath if we need to grab arguments from a child of the launcher // use the saved commandline from DetectedCommandline with GameArgs gameProc.StartInfo.Arguments = setHnd.DetectedCommandline + " " + setHnd.GameArgs; Program.Logger("OSOL", String.Format("Launching game with DetectedCommandline arguments, cmd: {0} {1} {2}", setHnd.GamePath, setHnd.DetectedCommandline, setHnd.GameArgs)); LaunchProcess(gameProc); } else if (!setHnd.CommandlineProxy) {// just launch the game since we've fallen through all the exclusive cases Program.Logger("OSOL", String.Format("Launching game, cmd: {0} {1}", setHnd.GamePath, setHnd.GameArgs)); gameProc.StartInfo.Arguments = setHnd.GameArgs; LaunchProcess(gameProc); if (setHnd.SkipLauncher && Settings.ValidatePath(setHnd.LauncherPath)) {// we still need LauncherPath tracking in Normal mode even though we didn't launch it ourselves (if defined) Program.Logger("OSOL", String.Format("Acquiring launcher handle because we didn't launch it ourselves...")); launcherProc = GetProcessTreeHandle(setHnd, launcherName, ref launcherType); launcherPID = launcherProc != null ? launcherProc.Id : 0; if (launcherPID > 0) { // do some waiting based on user tuneables to avoid BPM weirdness Program.Logger("OSOL", String.Format("Waiting {0}s for launcher process to load...", setHnd.PreGameLauncherWaitTime)); Thread.Sleep(setHnd.PreGameLauncherWaitTime * 1000); if (launcherType > -1) {// we can only send window messages if we have a window handle Program.BringToFront(launcherProc.MainWindowHandle); if (setHnd.MinimizeLauncher) { Program.MinimizeWindow(launcherProc.MainWindowHandle); } } } } } } // wait for the executable defined in GamePath (up to the ProcessAcquisitionTimeout) or use our MonitorPath if the user requests it gameProc = monitorPath.Length > 0 ? GetProcessTreeHandle(setHnd, monitorName, ref gameType) : GetProcessTreeHandle(setHnd, gameName, ref gameType); gamePID = gameProc != null ? gameProc.Id : 0; string _procPrio = setHnd.GameProcessPriority.ToString(); if (setHnd.CommandlineProxy && setHnd.DetectedCommandline.Length == 0) { /* * Our logic here is a bit confusing: * 1) If CommandlineProxy is enabled and we have no proxied arguments then grab them from the bound process * 2) Once we have arguments kill the existing bound process * 3) Launch a new process based on the GamePath with our freshly proxied arguments * 4) Save these proxied arguments to the INI under DetectedCommandline */ var _cmdLine = Program.GetCommandLineToString(gameProc, setHnd.GamePath); var _storedCmdline = setHnd.DetectedCommandline; Program.Logger("OSOL", String.Format("Detected arguments in [{0}]: {1}", gameProc.MainModule.ModuleName, _cmdLine)); if (!Program.CompareCommandlines(_storedCmdline, _cmdLine) && !Program.StringEquals(setHnd.GameArgs, _cmdLine)) {// only proxy arguments if our target arguments differ gameProc.Kill(); Thread.Sleep(setHnd.ProxyTimeout * 1000); gameProc.StartInfo.UseShellExecute = true; gameProc.StartInfo.FileName = setHnd.GamePath; gameProc.StartInfo.WorkingDirectory = Directory.GetParent(setHnd.GamePath).ToString(); gameProc.StartInfo.Arguments = setHnd.GameArgs + " " + _cmdLine; Program.Logger("OSOL", String.Format("Relaunching with proxied commandline, cmd: {0} {1} {2}", setHnd.GamePath, _cmdLine, setHnd.GameArgs)); LaunchProcess(gameProc); Thread.Sleep(setHnd.ProxyTimeout * 1000); // rebind to relaunched process gameProc = monitorPath.Length > 0 ? GetProcessTreeHandle(setHnd, monitorName, ref gameType) : GetProcessTreeHandle(setHnd, gameName, ref gameType); gamePID = gameProc != null ? gameProc.Id : 0; // save our newest active commandline for later Program.StoreCommandline(setHnd, iniHnd, _cmdLine); Program.Logger("OSOL", String.Format("Process arguments saved to INI: {0}", _cmdLine)); } } #endregion #region WaitForGame if (gamePID > 0 && gameType > -1) { // run our post-game launch commands after a configurable sleep Thread.Sleep((setHnd.PostGameCommandWaitTime - 1) * 1000); if (setHnd.GameProcessAffinity > 0) {// use our specified CPU affinity bitmask gameProc.ProcessorAffinity = (IntPtr)setHnd.GameProcessAffinity; Program.Logger("OSOL", String.Format("Setting game process CPU affinity to: {0}", BitmaskExtensions.AffinityToCoreString(setHnd.GameProcessAffinity))); } if (!Program.StringEquals(_procPrio, "Normal")) {// we have a custom process priority so let's use it gameProc.PriorityClass = setHnd.GameProcessPriority; Program.Logger("OSOL", String.Format("Setting game process priority to: {0}", setHnd.GameProcessPriority.ToString())); } if (setHnd.TerminateOSOLUponLaunch) {// since we've done all that's been asked we can quit out if requested Program.Logger("OSOL", "User requested self-termination after game launch, exiting now..."); Environment.Exit(0); } else { while (Program.IsRunningPID(gamePID)) {// spin while game is running Thread.Sleep(1000); } } Program.Logger("OSOL", String.Format("The {0} exited, moving on to clean up...", _launchType)); } else { Program.Logger("WARNING", String.Format("Could not find a {0} process by name: {1}", _launchType, Program.StringEquals("monitor", _launchType) ? gameName : monitorName)); } #endregion /* * Post-Game Cleanup */ #region PostGame if (launcherPID > 0 && launcherProc != null && !launcherProc.HasExited && !setHnd.DoNotClose) {// found the launcher left after the game exited Thread.Sleep(1000); // resend the message to minimize our launcher if (setHnd.MinimizeLauncher) { Program.MinimizeWindow(launcherProc.MainWindowHandle); } // let Origin sync with the cloud Thread.Sleep((setHnd.PostGameWaitTime - 1) * 1000); Program.Logger("OSOL", String.Format("Found launcher still running, cleaning up...", _launchType)); // finally, kill our launcher proctree Program.KillProcTreeByName(launcherName); } // ask a non-async delegate to run a process after the game and launcher exit Program.ExecuteExternalElevated(setHnd, setHnd.PostGameExec, setHnd.PostGameExecArgs); // make sure we sleep a bit to ensure the external process and launcher terminate properly Thread.Sleep(setHnd.ProxyTimeout * 1000); // clean up system tray if process related icons are leftover trayUtil.RefreshTrayArea(); #endregion }
public async Task ProcessLauncher(Settings setHnd, IniFile iniHnd) {// pass our Settings and IniFile contexts into this workhorse routine String launcherName = Path.GetFileNameWithoutExtension(setHnd.LauncherPath); String gameName = Path.GetFileNameWithoutExtension(setHnd.GamePath); String launcherMode = setHnd.LauncherMode; Process launcherProc = new Process(), gameProc = new Process(); ProcessObj gameProcObj = null, launcherProcObj = null; TrayIconUtil trayUtil = new TrayIconUtil(); // save our monitoring path for later String monitorPath = Settings.ValidatePath(setHnd.MonitorPath) ? setHnd.MonitorPath : String.Empty; String monitorName = Path.GetFileNameWithoutExtension(monitorPath); String _launchType = (monitorPath.Length > 0 ? "monitor" : "game"); // TODO: IMPLEMENT ASYNC TASK SYSTEM FOR LAUNCHER AND GAME! /* * Launcher Detection: * * 1) If LauncherPath not set skip to Step 8 * 2) If SkipLauncher is set skip to Step 8 * 3) Check if LauncherPath is actively running - relaunch via Steam->OSOL * 4) Execute pre-launcher delegate * 5) Execute launcher (using ShellExecute) - use LauncherURI if set (not ShellExecute) * 6) Hand off to GetProcessObj() for detection * a) GetProcessObj() loops on timeout until valid process is detected * b) ValidateProcessByName() attempts to validate Process and PID returns * c) Returns ProcessObj with correct PID, process type, and Process handle * 7) Perform post-launcher behaviors * 8) Continue to game */ #region LauncherDetection // only use validated launcher if CommandlineProxy is not enabled, Launcher is not forced, and we have no DetectedCommandline if (!String.IsNullOrEmpty(setHnd.LauncherURI) || !setHnd.SkipLauncher & Settings.ValidatePath(setHnd.LauncherPath) & (setHnd.ForceLauncher || !setHnd.CommandlineProxy || setHnd.DetectedCommandline.Length == 0)) { // check for running instance of launcher (relaunch if required) if (ProcessUtils.IsRunningByName(launcherName) && setHnd.ReLaunch) {// if the launcher is running before the game kill it so we can run it through Steam ProcessUtils.Logger("OSOL", "Found previous instance of launcher by name, killing and relaunching..."); ProcessUtils.KillProcTreeByName(launcherName); Thread.Sleep(setHnd.ProxyTimeout * 1000); // pause a moment for the launcher to close } // ask a delegate to run a process before the launcher and wait for it to return await Task.Run(() => ProcessUtils.ExecuteExternalElevated(setHnd, setHnd.PreLaunchExec, setHnd.PreLaunchExecArgs, 0) ); if (ProcessUtils.StringEquals(launcherMode, "URI") && !String.IsNullOrEmpty(setHnd.LauncherURI)) {// use URI launching as a mutually exclusive alternative to "Normal" launch mode (calls launcher->game) gameProc.StartInfo.UseShellExecute = true; gameProc.StartInfo.FileName = setHnd.LauncherURI; gameProc.StartInfo.Arguments = setHnd.GameArgs; ProcessUtils.Logger("OSOL", $"Launching URI: {setHnd.LauncherURI} {setHnd.GameArgs}"); ProcessUtils.LaunchProcess(gameProc); } else { launcherProc.StartInfo.UseShellExecute = true; launcherProc.StartInfo.FileName = setHnd.LauncherPath; launcherProc.StartInfo.WorkingDirectory = Directory.GetParent(setHnd.LauncherPath).ToString(); launcherProc.StartInfo.Arguments = setHnd.LauncherArgs; ProcessUtils.Logger("OSOL", $"Attempting to start the launcher: {setHnd.LauncherPath}"); ProcessUtils.LaunchProcess(launcherProc); } launcherProcObj = ProcessObj.GetProcessObj(setHnd, launcherName); launcherProc = launcherProcObj.ProcessRef; if (launcherProcObj.ProcessId > 0) { if (launcherProcObj.ProcessType > -1) {// we can only send window messages if we have a window handle WindowUtils.BringToFront(WindowUtils.HwndFromProc(launcherProc)); if (setHnd.MinimizeLauncher && launcherProc.MainWindowHandle != IntPtr.Zero) { WindowUtils.MinimizeWindow(WindowUtils.HwndFromProc(launcherProc)); } } // wait a bit for the launcher to stabilize ProcessMonitor launcherMonitor = new ProcessMonitor(launcherProcObj, setHnd.PreGameLauncherWaitTime); launcherProcObj = await launcherMonitor.InterruptibleMonitorAsync(); launcherProc = launcherProcObj.ProcessRef; } } #endregion /* * Game Process Detection: * * 1) Only launch GamePath if we're in "Normal" launch mode (pre-validated) * 2) Execute GamePath (or use Launcher if we have an exclusive case) * 3) Hand off to GetProcessObj() for detection * a) GetProcessObj() loops on timeout until valid process is detected * b) ValidateProcessByName() attempts to validate Process and PID returns * c) Returns ProcessObj with correct PID, process type, and Process handle * 4) If we're using CommandlineProxy attempt to detect the target process cmdline * a) If we've got a cmdline relaunch GamePath with it * 5) Do post-game-detection steps * 6) Hand off to MonitorProcess() for watching our launched game */ #region GameDetection if (ProcessUtils.StringEquals(launcherMode, "Normal")) {// we're not in URI or LauncherOnly modes - this is the default gameProc.StartInfo.UseShellExecute = true; gameProc.StartInfo.FileName = setHnd.GamePath; gameProc.StartInfo.WorkingDirectory = Directory.GetParent(setHnd.GamePath).ToString(); if (launcherProcObj.ProcessType == 1) { // we've detected Battle.net Launcher so let's ask the launcher politely to start the game gameProc.StartInfo.FileName = setHnd.LauncherPath; // use the launcher - look for the game below in GetProcessTreeHandle() gameProc.StartInfo.Arguments = setHnd.LauncherArgs; // these contain the game launch command ProcessUtils.Logger("OSOL", $"Detected Battle.net launcher, calling game via: {setHnd.LauncherPath} {setHnd.LauncherArgs}"); ProcessUtils.LaunchProcess(gameProc); } else if (setHnd.CommandlineProxy && setHnd.DetectedCommandline.Length > 0) {// avoid executing GamePath if we need to grab arguments from a child of the launcher // use the saved commandline from DetectedCommandline with GameArgs gameProc.StartInfo.Arguments = setHnd.DetectedCommandline + " " + setHnd.GameArgs; ProcessUtils.Logger("OSOL", $"Launching game with DetectedCommandline arguments, cmd: {setHnd.GamePath} {setHnd.DetectedCommandline} {setHnd.GameArgs}"); ProcessUtils.LaunchProcess(gameProc); } else if (!setHnd.CommandlineProxy) {// just launch the game since we've fallen through all the exclusive cases ProcessUtils.Logger("OSOL", $"Launching game, cmd: {setHnd.GamePath} {setHnd.GameArgs}"); gameProc.StartInfo.Arguments = setHnd.GameArgs; ProcessUtils.LaunchProcess(gameProc); if (setHnd.SkipLauncher && Settings.ValidatePath(setHnd.LauncherPath)) {// we still need LauncherPath tracking in Normal mode even though we didn't launch it ourselves (if defined) ProcessUtils.Logger("OSOL", "Acquiring launcher handle because we didn't launch it ourselves..."); launcherProcObj = ProcessObj.GetProcessObj(setHnd, launcherName); launcherProc = launcherProcObj.ProcessRef; if (launcherProcObj.ProcessId > 0) { if (launcherProcObj.ProcessType > -1) {// we can only send window messages if we have a window handle WindowUtils.BringToFront(WindowUtils.HwndFromProc(launcherProc)); if (setHnd.MinimizeLauncher) { WindowUtils.MinimizeWindow(WindowUtils.HwndFromProc(launcherProc)); } } ProcessMonitor launcherMonitor = new ProcessMonitor(launcherProcObj, setHnd.PreGameLauncherWaitTime); launcherProcObj = await launcherMonitor.InterruptibleMonitorAsync(); launcherProc = launcherProcObj.ProcessRef; } } } } // wait for the executable defined in GamePath (up to the ProcessAcquisitionTimeout) or use our MonitorPath if the user requests it gameProcObj = monitorPath.Length > 0 ? ProcessObj.GetProcessObj(setHnd, monitorName) : ProcessObj.GetProcessObj(setHnd, gameName); gameProc = gameProcObj.ProcessRef; string _procPrio = setHnd.GameProcessPriority.ToString(); if (setHnd.CommandlineProxy && setHnd.DetectedCommandline.Length == 0) { /* * Our logic here is a bit confusing: * 1) If CommandlineProxy is enabled and we have no proxied arguments then grab them from the bound process * 2) Once we have arguments kill the existing bound process * 3) Launch a new process based on the GamePath with our freshly proxied arguments * 4) Save these proxied arguments to the INI under DetectedCommandline */ var _cmdLine = ProcessUtils.GetCommandLineToString(gameProc, setHnd.GamePath); var _storedCmdline = setHnd.DetectedCommandline; ProcessUtils.Logger("OSOL", $"Detected arguments in [{gameProc.MainModule.ModuleName}]: {_cmdLine}"); if (!ProcessUtils.CompareCommandlines(_storedCmdline, _cmdLine) && !ProcessUtils.StringEquals(setHnd.GameArgs, _cmdLine)) {// only proxy arguments if our target arguments differ gameProc.Kill(); Thread.Sleep(setHnd.ProxyTimeout * 1000); gameProc.StartInfo.UseShellExecute = true; gameProc.StartInfo.FileName = setHnd.GamePath; gameProc.StartInfo.WorkingDirectory = Directory.GetParent(setHnd.GamePath).ToString(); gameProc.StartInfo.Arguments = setHnd.GameArgs + " " + _cmdLine; ProcessUtils.Logger("OSOL", $"Relaunching with proxied commandline, cmd: {setHnd.GamePath} {_cmdLine} {setHnd.GameArgs}"); ProcessUtils.LaunchProcess(gameProc); Thread.Sleep(setHnd.ProxyTimeout * 1000); // rebind to relaunched process gameProcObj = monitorPath.Length > 0 ? ProcessObj.GetProcessObj(setHnd, monitorName) : ProcessObj.GetProcessObj(setHnd, gameName); gameProc = gameProcObj.ProcessRef; // save our newest active commandline for later ProcessUtils.StoreCommandline(setHnd, iniHnd, _cmdLine); ProcessUtils.Logger("OSOL", $"Process arguments saved to INI: {_cmdLine}"); } } #endregion #region WaitForGame if (gameProcObj != null && gameProcObj.ProcessId > 0) { if (setHnd.GameProcessAffinity > 0) {// use our specified CPU affinity bitmask gameProc.ProcessorAffinity = (IntPtr)setHnd.GameProcessAffinity; ProcessUtils.Logger("OSOL", $"Setting game process CPU affinity to: {BitmaskExtensions.AffinityToCoreString(setHnd.GameProcessAffinity)}" ); } if (!ProcessUtils.StringEquals(_procPrio, "Normal")) {// we have a custom process priority so let's use it gameProc.PriorityClass = setHnd.GameProcessPriority; ProcessUtils.Logger("OSOL", $"Setting game process priority to: {setHnd.GameProcessPriority.ToString()}"); } if (setHnd.TerminateOSOLUponLaunch) {// since we've done all that's been asked we can quit out if requested ProcessUtils.Logger("OSOL", "User requested self-termination after game launch, exiting now..."); Environment.Exit(0); } else { // monitor our game process until it exits ProcessMonitor gameMonitor = new ProcessMonitor(gameProcObj, setHnd.InterProcessAcquisitionTimeout); await gameMonitor.MonitorAsync(); ProcessUtils.Logger("OSOL", $"Game exited, moving on to clean up after {setHnd.PostGameWaitTime}s..."); } } else { string _procName = ProcessUtils.StringEquals("monitor", _launchType) ? gameName : monitorName; ProcessUtils.Logger("WARNING", $"Could not find a process by name ({_procName}.exe), exiting..."); } #endregion /* * Post-Game Cleanup */ #region PostGame if (launcherProcObj.ProcessId > 0 && !launcherProcObj.ProcessRef.HasExited && !setHnd.DoNotClose) { // resend the message to minimize our launcher if (setHnd.MinimizeLauncher && launcherProc.MainWindowHandle != IntPtr.Zero) { WindowUtils.MinimizeWindow(WindowUtils.HwndFromProc(launcherProc)); } // let Origin sync with the cloud await Task.Delay(setHnd.PostGameWaitTime * 1000); ProcessUtils.Logger("OSOL", "Found launcher still running, cleaning up..."); // finally, kill our launcher proctree ProcessUtils.KillProcTreeByName(launcherName); } // ask a delegate to run a process after the game exits and wait for it to return (with an initial standoff period) await Task.Run(() => ProcessUtils.ExecuteExternalElevated(setHnd, setHnd.PostGameExec, setHnd.PostGameExecArgs, setHnd.PostGameCommandWaitTime) ); // clean up system tray if process related icons are leftover trayUtil.RefreshTrayArea(); #endregion }