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(); } }
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); }
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); }
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); }
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); } }
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); }
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(); }
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; }
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; } }
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 }
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); } } }