Example #1
0
        public bool Refresh()
        {
            var _procRefs = ProcessUtils.GetProcessesByName(this.ProcessName);

            if (_procRefs != null && _procRefs.Count > 0)
            {
                foreach (Process p in _procRefs)
                {     // check each returned process for validity
                    if (ProcessUtils.IsValidProcess(p))
                    { // prefer a process with a title and handle
                        ProcessRef  = p;
                        ProcessId   = ProcessRef.Id;
                        ProcessType = WindowUtils.DetectWindowType(ProcessRef);
                        return(true); // return early on the first match
                    }
                }
            }
            return(false);
        }
 public Settings(bool testable = false)
 {
     Data = new SettingsData();
     if (!testable && !CheckINI())
     {
         ProcessUtils.Logger("WARNING", "Config file partially invalid or doesn't exist, re-stubbing...");
         if (!MigrateLegacyConfig())
         {// rebuild from scratch if migrate fails
             CreateINIFromInstance();
             PathChooser();
         }
         else
         {
             PathChooser();
         }
     }
     else if (!testable)
     {
         ParseToInstance();
     }
 }
Example #3
0
        private static string GetClassNameOfWindow(IntPtr hWnd)
        {
            string        className = "";
            StringBuilder classText = null;

            try
            {
                int cls_max_length = 1000;
                classText = new StringBuilder("", cls_max_length + 5);
                GetClassName(hWnd, classText, cls_max_length + 2);

                if (!string.IsNullOrWhiteSpace(classText.ToString()))
                {
                    className = classText.ToString();
                }
            }
            catch (Exception ex)
            {
                ProcessUtils.Logger("EXCEPTION", ex.Message);
            }
            return(className);
        }
Example #4
0
        private static string GetCaptionOfWindow(IntPtr hWnd)
        {
            string        caption    = "";
            StringBuilder windowText = null;

            try
            {
                int max_length = GetWindowTextLength(hWnd);
                windowText = new StringBuilder("", max_length + 5);
                GetWindowText(hWnd, windowText, max_length + 2);

                if (!string.IsNullOrWhiteSpace(windowText.ToString()))
                {
                    caption = windowText.ToString();
                }
            }
            catch (Exception ex)
            {
                ProcessUtils.Logger("EXCEPTION", ex.Message);
            }
            return(caption);
        }
Example #5
0
        private async Task <bool> SpinnerAsync()
        {
            Stopwatch _isw = new Stopwatch();

            _isw.Start();
            if (this.Process == null || this.Process.ProcessRef != null && this.Process.ProcessRef.HasExited)
            {// sanity check
                ProcessUtils.Logger("MONITOR", $"The target process {this.Process.ProcessName}.exe could not be found!");
                this.IsRunning = false;
                return(true);
            }

            while (!this.Process.ProcessRef.HasExited)
            {
                this.IsRunning = true;
                await Task.Delay(1000);
            }
            _isw.Stop();
            this.IsRunning = false;
            ProcessUtils.Logger("MONITOR", $"Process exited after {ConvertElapsedToString(_isw.ElapsedMilliseconds)} attempting to reaquire {this.Process.ProcessName}.exe");
            return(false);
        }
Example #6
0
        public async Task <ProcessObj> InterruptibleMonitorAsync()
        {// always returns a ProcessObj after a monitoring timeout (could be old if unsuccessful)
            ProcessUtils.Logger("MONITOR", $"Monitoring process {this.Process.ProcessName}.exe for {this.Timeout}s");
            while (this.GlobalElapsedTimer < this.Timeout * 1000)
            {// reacquire within a timeout (non-blocking)
                await this.IncrementTimerAsync();

                if (this.Process.ProcessRef.HasExited)
                {// try to reacquire process by name
                    var _proc = new ProcessObj(this.Process.ProcessName);
                    if (_proc != null && _proc.IsValid)
                    {
                        this.GlobalElapsedTimer = 0;
                        ProcessUtils.Logger("MONITOR", $"Reacquired a matching target process ({_proc.ProcessName}.exe [{_proc.ProcessId}])");
                        this.Process   = _proc; // don't assign a dead process
                        this.IsRunning = true;
                    }
                    else
                    {
                        this.IsRunning = false;
                    }
                }
                else
                {
                    this.IsRunning = true;
                }
            }
            // report our results once the timer expires
            if (this.IsRunning)
            {
                ProcessUtils.Logger("MONITOR", $"Target process ({this.Process.ProcessName}.exe [{this.Process.ProcessId}]) is still running after {this.Timeout}s");
            }
            else
            {
                ProcessUtils.Logger("MONITOR", $"Timed out after {this.Timeout}s while monitoring the target process: {this.Process.ProcessName}.exe");
            }
            return(this.Process);
        }
        public static bool ValidateINI(Settings setHnd, IniFile iniHnd, String iniFilePath)
        {// validate while reading from ini - filling in defaults where sensible
            setHnd.LauncherPath = ValidateString(iniHnd, String.Empty, "LauncherPath", "LauncherPath", "Paths");
            setHnd.LauncherArgs = ValidateString(iniHnd, String.Empty, "LauncherArgs", "LauncherArgs", "Paths");
            setHnd.LauncherURI  = ValidateString(iniHnd, String.Empty, "LauncherURI", "LauncherURI", "Paths");

            setHnd.GamePath    = ValidateString(iniHnd, String.Empty, "GamePath", "GamePath", "Paths");
            setHnd.GameArgs    = ValidateString(iniHnd, String.Empty, "GameArgs", "GameArgs", "Paths");
            setHnd.MonitorPath = ValidateString(iniHnd, String.Empty, "MonitorPath", "MonitorPath", "Paths");

            // support for saving a commandline grabbed via CommandlineProxy
            setHnd.DetectedCommandline = ValidateString(iniHnd, String.Empty, setHnd.DetectedCommandline, "DetectedCommandline", "Paths");

            // special case - check launchermode options
            if (iniHnd.KeyPopulated("LauncherMode", "Options") &&
                ProcessUtils.StringEquals(iniHnd.ReadString("LauncherMode", "Options"), "Normal") ||
                ProcessUtils.StringEquals(iniHnd.ReadString("LauncherMode", "Options"), "URI") ||
                ProcessUtils.StringEquals(iniHnd.ReadString("LauncherMode", "Options"), "LauncherOnly"))
            {
                /*
                 * "LauncherMode" can have three options:
                 *     "Normal": launches Origin, launches the game (using the options provided by the user),
                 *         waits for the game to close, then closes Origin.
                 *     "URI": launches the user specified launcher, executes the user specified launcher URI,
                 *         waits for the user specified game to start, then closes the launcher when the game
                 *         exits.
                 *     "LauncherOnly": launches Origin, waits for the game to be executed by the user, waits
                 *         for the game to close, then closes Origin.
                 *
                 *     Note: 'LauncherOnly' is intended to provide extra compatibility when some games don't
                 *     work properly with the BPM overlay. This is to work around a Steam regression involving
                 *     hooking Origin titles launched through the Origin2 launcher.
                 */
                setHnd.LauncherMode = ValidateString(iniHnd, "Normal", setHnd.LauncherMode, "LauncherMode", "Options");
            }
            else
            {// auto-correct
                iniHnd.Write("LauncherMode", "Normal", "Options");
                setHnd.LauncherMode = "Normal";
            }

            // pre-launcher/post-game script support
            setHnd.PreLaunchExec     = ValidateString(iniHnd, String.Empty, setHnd.PreLaunchExec, "PreLaunchExec", "Options");
            setHnd.PreLaunchExecArgs = ValidateString(iniHnd, String.Empty, setHnd.PreLaunchExecArgs, "PreLaunchExecArgs", "Options");
            setHnd.PostGameExec      = ValidateString(iniHnd, String.Empty, setHnd.PostGameExec, "PostGameExec", "Options");
            setHnd.PostGameExecArgs  = ValidateString(iniHnd, String.Empty, setHnd.PostGameExecArgs, "PostGameExecArgs", "Options");

            // treat ints differently (use defaults if these don't exist)
            setHnd.ProxyTimeout                   = ValidateInt(iniHnd, 3, "ProxyTimeout", "Options");
            setHnd.PreGameLauncherWaitTime        = ValidateInt(iniHnd, 7, "PreGameLauncherWaitTime", "Options");
            setHnd.PostGameWaitTime               = ValidateInt(iniHnd, 7, "PostGameWaitTime", "Options");
            setHnd.PostGameCommandWaitTime        = ValidateInt(iniHnd, 5, "PostGameCommandWaitTime", "Options");
            setHnd.ProcessAcquisitionTimeout      = ValidateInt(iniHnd, 120, "ProcessAcquisitionTimeout", "Options");
            setHnd.InterProcessAcquisitionTimeout = ValidateInt(iniHnd, 10, "InterProcessAcquisitionTimeout", "Options");
            setHnd.ProcessAcquisitionAttempts     = ValidateInt(iniHnd, 5, "ProcessAcquisitionAttempts", "Options");

            // parse strings into bools
            // Default to closing the previously detected launcher PID
            setHnd.ReLaunch = ValidateBool(iniHnd, true, "ReLaunch", "Options");
            // Default to execute LauncherPath (if defined)
            setHnd.SkipLauncher = ValidateBool(iniHnd, false, "SkipLauncher", "Options");
            // Default to closing the detected launcher PID when a game exits
            setHnd.DoNotClose = ValidateBool(iniHnd, false, "DoNotClose", "Options");
            // Default to not launch LauncherPath separate from GamePath
            setHnd.ForceLauncher = ValidateBool(iniHnd, false, "ForceLauncher", "Options");
            // Default to leaving the launcher window alone after detecting it
            setHnd.MinimizeLauncher = ValidateBool(iniHnd, false, "MinimizeLauncher", "Options");
            // Default to not proxying the commandline from a running instance of the game/monitor executable
            setHnd.CommandlineProxy = ValidateBool(iniHnd, false, "CommandlineProxy", "Options");
            // Default to not running external pre-post processes with elevated privs
            setHnd.ElevateExternals = ValidateBool(iniHnd, false, "ElevateExternals", "Options");
            // Default to disallowing OSOL from suiciding after game launch
            setHnd.TerminateOSOLUponLaunch = ValidateBool(iniHnd, false, "TerminateOSOLUponLaunch", "Options");

            // Default to no CPU core affinity (internally used as a bitmask - string, int, or hex)
            setHnd.GameProcessAffinity = ValidateBitmask(iniHnd, 0, "GameProcessAffinity", "Options");
            setHnd.GameProcessPriority = ValidatePriority(iniHnd, String.Empty, "GameProcessPriority", "Options");

            if (ValidatePath(setHnd.GamePath))
            {
                return(true); // continue if the GamePath works
            }
            return(false);
        }
        public static bool CreateINI(Settings setHnd, IniFile iniHnd)
        {                                                     // reusable initializer for recreating fresh INI
            if (!ValidatePath(iniHnd.Path))
            {                                                 // either our ini is invalid or doesn't exist
                File.WriteAllText(iniHnd.Path, String.Empty); // overwrite ini

                // paths
                iniHnd.Write("LauncherPath", String.Empty, "Paths");
                iniHnd.Write("LauncherArgs", String.Empty, "Paths");
                iniHnd.Write("LauncherURI", String.Empty, "Paths");
                iniHnd.Write("GamePath", String.Empty, "Paths");
                iniHnd.Write("GameArgs", String.Empty, "Paths");
                iniHnd.Write("MonitorPath", String.Empty, "Paths");
                iniHnd.Write("DetectedCommandline", String.Empty, "Paths");

                // options
                iniHnd.Write("LauncherMode", "Normal", "Options");
                iniHnd.Write("PreLaunchExec", String.Empty, "Options");
                iniHnd.Write("PreLaunchExecArgs", String.Empty, "Options");
                iniHnd.Write("PostGameExec", String.Empty, "Options");
                iniHnd.Write("PostGameExecArgs", String.Empty, "Options");

                // integer options (sensible defaults)
                iniHnd.Write("ProxyTimeout", "3", "Options");                    //3s
                iniHnd.Write("PreGameLauncherWaitTime", "7", "Options");         //7s
                iniHnd.Write("PostGameWaitTime", "7", "Options");                //7s
                iniHnd.Write("PostGameCommandWaitTime", "5", "Options");         //5s
                iniHnd.Write("ProcessAcquisitionTimeout", "120", "Options");     //2mins
                iniHnd.Write("InterProcessAcquisitionTimeout", "10", "Options"); //10s
                iniHnd.Write("ProcessAcquisitionAttempts", "5", "Options");      //5 attempts * ProxyTimeout (~15s)

                // options as parsed strings
                // Kill and relaunch detected launcher PID before game
                iniHnd.Write("ReLaunch", "True", "Options");
                // Do not skip executing LauncherPath by default
                iniHnd.Write("SkipLauncher", "False", "Options");
                // Do not kill detected launcher PID after game exits
                iniHnd.Write("DoNotClose", "False", "Options");
                // Do not launch LauncherPath separate from GamePath
                iniHnd.Write("ForceLauncher", "False", "Options");
                // Do not minimize launcher on process detection
                iniHnd.Write("MinimizeLauncher", "False", "Options");
                // Do not copy the commandline from a previous instance of the game/monitor executable
                iniHnd.Write("CommandlineProxy", "False", "Options");
                // Do not attempt to run external pre-post processes with elevated privs
                iniHnd.Write("ElevateExternals", "False", "Options");
                // Do not set a CPU affinity mask override by default
                iniHnd.Write("GameProcessAffinity", String.Empty, "Options");
                // Do not set a target game process priority by default
                iniHnd.Write("GameProcessPriority", String.Empty, "Options");
                // Disable OSOL suicide after game process by default
                iniHnd.Write("TerminateOSOLUponLaunch", "False", "Options");

                ProcessUtils.Logger("OSOL", "Created the INI file from stubs after we couldn't find it...");
                return(false);
            }
            else
            {
                return(true);
            }
        }
Example #9
0
        public static int DetectWindowType(Process proc)
        {
            int result = -1;

            if (proc != null)
            {
                var    _hWnd    = GetHWND(proc);
                string haystack = NativeProcessUtils.GetProcessModuleName(proc.Id);
                if (_hWnd != IntPtr.Zero)
                {
                    // excluded windows
                    if (ProcessUtils.OrdinalContains("EasyAntiCheat_launcher", haystack))
                    {
                        return(-1);
                    }

                    // initially try to resolve by looking up className and windowTitle
                    if (MatchWindowDetails("Epic Games Launcher", "UnrealWindow", _hWnd))
                    {
                        result = 4; // Epic Games Launcher [Type 4]
                    }
                    else if (MatchWindowDetails("Uplay", "uplay_main", _hWnd))
                    {
                        result = 3; // Uplay [Type 3]
                    }
                    else if (MatchWindowDetails("Origin", "Qt5QWindowIcon", _hWnd))
                    {
                        result = 2; // Origin [Type 2]
                    }
                    else if (MatchWindowDetails("Blizzard Battle.net", "Qt5QWindowOwnDCIcon", _hWnd))
                    {
                        result = 1; // Blizzard Battle.net [Type 1]
                    }
                    else if (WindowHasDetails(_hWnd) || IsWindow(_hWnd))
                    {
                        result = 0; // catch all other obvious windows
                    }
                    if (result > -1)
                    {
                        return(result);
                    }
                }
                // fallback to detection by module name
                if (ProcessUtils.OrdinalContains("EpicGamesLauncher", haystack))
                {
                    result = 4;
                }
                else if (ProcessUtils.OrdinalContains("upc", haystack))
                {
                    result = 3;
                }
                else if (ProcessUtils.OrdinalContains("Origin", haystack))
                {
                    result = 2;
                }
                else if (ProcessUtils.OrdinalContains("Battle.net", haystack))
                {
                    result = 1;
                }
            }
            return(result);
        }
        private void OnProcessHardExit(ProcessMonitor m, ProcessEventArgs e)
        {
            if (e.TargetProcess != null)
            {
                ProcessUtils.Logger($"MONITOR@{ProcessName}",
                                    $"Timed out after {ProcessUtils.ElapsedToString(e.Elapsed)} searching for a matching process: {e.ProcessName}.exe");
            }
            else
            {
                ProcessUtils.Logger($"MONITOR@{ProcessName}",
                                    $"Could not detect a running process after waiting {ProcessUtils.ElapsedToString(e.Elapsed)}...");
            }

            Stop();
            ProcessHardExit?.Invoke(m, e);
        }
Example #11
0
 private void OnLauncherExited(object sender, ProcessEventArgs e)
 {// edge case if launcher times out (or closes) before game launches
     ProcessUtils.Logger("OSOL",
                         $"Launcher could not be acquired within {ProcessUtils.ElapsedToString(e.Elapsed)}, cleaning up...");
     OnClosing();
 }
Example #12
0
        private async void OnLauncherAcquired(object sender, ProcessEventArgs e)
        {
            // collect launcher information for collision avoidance
            int  _type    = LauncherPL?.ProcWrapper?.ProcessType ?? -1;
            bool _running = (bool)LauncherMonitor?.IsRunning();
            int  _aPID    = e?.AvoidPID ?? 0;

            // MinimizeWindow after acquisition to prevent issues with ProcessType() fetch
            if (SetHnd.Options.MinimizeLauncher && LauncherPL.ProcWrapper.IsRunning())
            {
                WindowUtils.MinimizeWindow(LauncherPL.ProcWrapper.Hwnd);
            }

            if (!SetHnd.Options.SkipLauncher && LauncherPathValid && LauncherPL != null)
            {// pause to let the launcher process stabilize after being hooked
                ProcessUtils.Logger("OSOL",
                                    $"Launcher detected (type {_type}), preparing to launch game in {SetHnd.Options.PreGameLauncherWaitTime}s...");
                await Task.Delay(SetHnd.Options.PreGameLauncherWaitTime * 1000);
            }

            if (SetHnd.Options.SkipLauncher)
            {                        // ignore AutoGameLaunch option explicitly here
                if (LauncherURIMode) // URI mode
                {
                    GamePL = new ProcessLauncher(
                        SetHnd.Paths.LauncherURI, "",
                        avoidProcName: LauncherName,
                        delayTime: SetHnd.Options.PreGameWaitTime,
                        monitorName: GameName
                        );
                }
                else  // normal SkipLauncher behavior
                {
                    GamePL = new ProcessLauncher(
                        SetHnd.Paths.GamePath,
                        SetHnd.Paths.GameArgs,
                        avoidProcName: LauncherName,
                        delayTime: SetHnd.Options.PreGameWaitTime,
                        monitorName: MonitorName
                        );
                }
                await GamePL.Launch();
            }
            else
            {
                if (_running && LauncherURIMode) // URIs
                {
                    GamePL = new ProcessLauncher(
                        SetHnd.Paths.LauncherURI, "",
                        avoidProcName: LauncherName,
                        delayTime: SetHnd.Options.PreGameWaitTime,
                        avoidPID: _aPID,
                        monitorName: GameName
                        );
                }
                else if (_running && _type == 1) // Battle.net (relaunch LauncherArgs)
                {
                    GamePL = new ProcessLauncher(
                        SetHnd.Paths.LauncherPath,
                        SetHnd.Paths.LauncherArgs,
                        avoidProcName: LauncherName,
                        delayTime: SetHnd.Options.PreGameWaitTime,
                        avoidPID: _aPID,
                        monitorName: GameName
                        );
                }
                else if (LauncherPathValid && _running) // normal behavior
                {
                    GamePL = new ProcessLauncher(
                        SetHnd.Paths.GamePath,
                        SetHnd.Paths.GameArgs,
                        avoidProcName: LauncherName,
                        delayTime: SetHnd.Options.PreGameWaitTime,
                        avoidPID: _aPID,
                        monitorName: MonitorName
                        );
                }
                if (GamePL != null && (LauncherPathValid && _running || SetHnd.Options.AutoGameLaunch))
                {
                    await GamePL?.Launch(); // only launch if safe to do so
                }
                else if (LauncherPathValid && LauncherMonitor.IsRunning())
                {
                    ProcessUtils.Logger("OSOL", "AutoGameLaunch is false, waiting for user to launch game before timing out...");
                }
            }

            GameMonitor = new ProcessMonitor(
                GamePL,
                SetHnd.Options.ProcessAcquisitionTimeout,
                SetHnd.Options.InterProcessAcquisitionTimeout
                );
            GameMonitor.ProcessAcquired += OnGameAcquired;
            GameMonitor.ProcessHardExit += OnGameExited;
        }
Example #13
0
        private async void OnLauncherAcquired(object sender, ProcessEventArgs e)
        {
            int  _type    = -1;
            bool _running = false;
            int  _aPID    = 0;

            if (LauncherPL != null && LauncherMonitor != null)
            {// collect launcher information for collision avoidance
                _type    = LauncherPL?.ProcWrapper?.ProcessType ?? -1;
                _running = LauncherMonitor.IsRunning();
                _aPID    = e?.AvoidPID ?? 0;
            }

            if (LauncherPL != null && LauncherPL.ProcWrapper.IsRunning())
            {
                // MinimizeWindow after acquisition to prevent issues with ProcessType() fetch
                if (SetHnd.Options.MinimizeLauncher)
                {
                    WindowUtils.MinimizeWindow(LauncherPL.ProcWrapper.Hwnd);
                }

                // pause to let the launcher process stabilize after being hooked
                if (!SetHnd.Options.SkipLauncher)
                {
                    if (GamePathValid && SetHnd.Options.AutoGameLaunch)
                    {
                        ProcessUtils.Logger("OSOL",
                                            $"Launcher detected (type {_type}), preparing to launch game in {SetHnd.Options.PreGameLauncherWaitTime}s...");
                    }
                    else
                    {
                        ProcessUtils.Logger("OSOL",
                                            $"Launcher detected (type {_type}), skipping GamePath, monitoring...");
                    }
                    await Task.Delay(SetHnd.Options.PreGameLauncherWaitTime * 1000);
                }
            }

            if (SetHnd.Options.SkipLauncher && GamePathValid || LauncherURIMode)
            {                        // ignore AutoGameLaunch option explicitly here
                if (LauncherURIMode) // URI mode
                {
                    GamePL = new ProcessLauncher(
                        SetHnd.Paths.LauncherURI, "",
                        avoidProcName: LauncherName,
                        delayTime: SetHnd.Options.PreGameWaitTime,
                        monitorName: GameName
                        );
                }
                else  // normal SkipLauncher behavior
                {
                    GamePL = new ProcessLauncher(
                        SetHnd.Paths.GamePath,
                        SetHnd.Paths.GameArgs,
                        avoidProcName: LauncherName,
                        delayTime: SetHnd.Options.PreGameWaitTime,
                        monitorName: MonitorName
                        );
                }

                if (GamePL != null)
                {
                    await GamePL.Launch();
                }
            }
            else
            {
                if (_running && LauncherURIMode) // URIs
                {
                    GamePL = new ProcessLauncher(
                        SetHnd.Paths.LauncherURI, "",
                        avoidProcName: LauncherName,
                        delayTime: SetHnd.Options.PreGameWaitTime,
                        avoidPID: _aPID,
                        monitorName: GameName
                        );
                }
                else if (_running && _type == 1) // Battle.net (relaunch LauncherArgs)
                {
                    // Battle.net v1
                    GamePL = new ProcessLauncher(
                        SetHnd.Paths.LauncherPath,
                        SetHnd.Paths.LauncherArgs,
                        avoidProcName: LauncherName,
                        delayTime: SetHnd.Options.PreGameWaitTime,
                        avoidPID: _aPID,
                        monitorName: GameName
                        );

                    // Battle.net v2 doesn't launch via LauncherArgs so send Enter to the launcher
                    if (ProcessUtils.OrdinalContains("productcode=", SetHnd.Paths.LauncherArgs))
                    {
                        WindowUtils.SendEnterToForeground(LauncherPL.ProcWrapper.Hwnd);
                    }
                }
                else if (GamePathValid) // normal behavior
                {
                    GamePL = new ProcessLauncher(
                        SetHnd.Paths.GamePath,
                        SetHnd.Paths.GameArgs,
                        avoidProcName: LauncherName,
                        delayTime: SetHnd.Options.PreGameWaitTime,
                        avoidPID: _aPID,
                        monitorName: MonitorName
                        );
                }
                else if (!_running)
                { // edge case for !AutoGameLaunch while LauncherPathValid/GamePathValid
                    ProcessUtils.Logger("FATAL", "Fell through all launch attempts, this should not happen!");
                    Environment.Exit(0);
                }

                if (GamePL != null && GamePathValid && !SetHnd.Options.AutoGameLaunch)
                {
                    await GamePL?.Launch(NoLaunch : true); // monitor passively
                }
                else if (GamePL != null && (LauncherPathValid && _running || SetHnd.Options.AutoGameLaunch))
                {
                    await GamePL?.Launch(); // launch if safe to do so
                }
                else if (LauncherPathValid && LauncherMonitor.IsRunning())
                {
                    ProcessUtils.Logger("OSOL", $"AutoGameLaunch is false, continuing to monitor existing processes...");
                }
            }

            if (GamePL != null)
            { // monitor only if safe to do so
                GameMonitor = new ProcessMonitor(
                    GamePL,
                    SetHnd.Options.ProcessAcquisitionTimeout,
                    SetHnd.Options.InterProcessAcquisitionTimeout
                    );
                GameMonitor.ProcessAcquired += OnGameAcquired;
                GameMonitor.ProcessHardExit += OnGameExited;
            }
        }
Example #14
0
        public static bool TryParseAffinity(string bitmask, out long result)
        {// convert bitmask string to ulong internally
            result = 0;
            bool _isHT = IsCPUHyperthreaded(out int _physCores);
            // for sanity cap our max input to 32 cores (core0 - core31)
            var maxResult = 0xFFFFFFFF;

            // provide shortcuts for common affinity masks (be smart about HT)
            if (ProcessUtils.OrdinalEquals(bitmask, "DualCore"))
            {// avoid CPU0 if threaded
                result = _isHT ? 0xA : 0x5;
                ProcessUtils.Logger("OSOL", $"Parsed core mask to: {AffinityToCoreString(result)}");
                return(true);
            }
            else if (ProcessUtils.OrdinalEquals(bitmask, "QuadCore"))
            {
                result = _isHT ? 0xAA : 0xF;
                ProcessUtils.Logger("OSOL", $"Parsed core mask to: {AffinityToCoreString(result)}");
                return(true);
            }
            else if (_isHT && ProcessUtils.OrdinalEquals(bitmask, "DisableHT"))
            {
                long?_aCores = 0;

                for (int i = 0; i <= _physCores * 2; i++)
                {        // loop through and accumulate bits
                    _aCores += (1 << i);
                    i++; // .. for every other logical core
                }

                result = (long)_aCores;
                ProcessUtils.Logger("OSOL", $"Setting core mask to: {AffinityToCoreString(result)}");
                return(true);
            }
            else if (!string.IsNullOrEmpty(bitmask) && !ProcessUtils.OrdinalEquals(bitmask, "DisableHT"))
            {// just convert what's there if possible
                // pass string along to TryParseCorestring first
                if (bitmask.Length > 1 &&
                    ProcessUtils.OrdinalContains(",", bitmask) && TryParseCoreString(bitmask, out long _stringResult))
                {                           // try parsing as a core string - ints delimited by commas
                    ProcessUtils.Logger("OSOL", $"Parsed core mask: {AffinityToCoreString(_stringResult)}");
                    result = _stringResult; // copy our output externally
                }
                else if (bitmask.Length > 0 &&
                         !ProcessUtils.OrdinalContains(",", bitmask) && long.TryParse(bitmask, out result))
                {// attempt to parse using ulong conversion
                    ProcessUtils.Logger("OSOL", $"Parsed number mask to cores: {AffinityToCoreString(result)}");
                }
                else if (bitmask.Length > 1 &&
                         ProcessUtils.OrdinalContains("0x", bitmask) &&
                         Int64.TryParse(bitmask.Replace("0x", ""), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result))
                {// last ditch attempt to parse using hex conversion (in ordinal/invariant mode)
                    ProcessUtils.Logger("OSOL", $"Parsed hex mask to cores: {AffinityToCoreString(result)}");
                }

                // sanity check our max return
                if (result > maxResult)
                {
                    result = maxResult;
                }

                return(result >= 0 ? true : false);
            }
            else if (!string.IsNullOrEmpty(bitmask))
            {// default to single core if nothing else is a valid parse
                result = 1;
                return(true);
            }

            return(false);
        }
        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
        }
Example #16
0
        private static void Main(string[] args)
        {
            // get our current mutex id based off our AssemblyInfo.cs
            string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
            string mutexId = $"Global\\{{{appGuid}}}";

            // simple global mutex, courtesy of: https://stackoverflow.com/a/1213517
            using (var mutex = new Mutex(false, mutexId))
            {
                try
                {
                    try
                    {
                        if (!mutex.WaitOne(TimeSpan.FromSeconds(1), false))
                        {
                            Environment.Exit(0);
                        }
                    }
                    catch (AbandonedMutexException)
                    {
                        ProcessUtils.Logger("MUTEX", "Mutex is held by another instance, but seems abandoned!");
                        Environment.Exit(0);
                    }

                    /*
                     * Run our actual entry point here...
                     */
                    Application.EnableVisualStyles(); // enable DPI awareness
                    Application.SetCompatibleTextRenderingDefault(false);

                    if (ProcessUtils.CliArgExists(args, "help") || ProcessUtils.CliArgExists(args, "?"))
                    {// display an INI settings overview if run with /help or /?
                        DisplayHelpDialog();
                    }
                    else
                    {
                        ProcessTracking procTrack = new ProcessTracking();
                        Settings        curSet    = new Settings();
                        // path to our local config
                        IniFile iniFile = new IniFile(appName + ".ini");

                        // overwrite/create log upon startup
                        File.WriteAllText(appName + "_Log.txt", String.Empty);
                        ProcessUtils.Logger("NOTE", "OSOL is running as: " + appName);

                        if (Settings.CheckINI(iniFile) &&
                            Settings.ValidateINI(curSet, iniFile, iniFile.Path))
                        {
                            try
                            {
                                procTrack.ProcessLauncher(curSet, iniFile).Wait();
                            }
                            catch (AggregateException ae)
                            {
                                foreach (var ex in ae.InnerExceptions)
                                {
                                    ProcessUtils.Logger("EXCEPTION", $"{ex.ToString()}: {ex.Message}");
                                }
                            }
                        }
                        else
                        {// ini doesn't match our comparison, recreate from stubs
                            ProcessUtils.Logger("WARNING", "Config file partially invalid or doesn't exist, re-stubbing...");
                            Settings.CreateINI(curSet, iniFile);
                            Settings.ValidateINI(curSet, iniFile, iniFile.Path);
                            Settings.PathChooser(curSet, iniFile);
                        }
                    }
                }
                finally
                {
                    mutex.ReleaseMutex();
                    ProcessUtils.Logger("OSOL", "Exiting...");
                    Environment.Exit(0);
                }
            }
        }