diff --git a/UWPHook/AppEntry.cs b/UWPHook/AppEntry.cs index 8df6091..3fe7736 100644 --- a/UWPHook/AppEntry.cs +++ b/UWPHook/AppEntry.cs @@ -35,6 +35,16 @@ namespace UWPHook set { _name = value; } } + private string _executable; + /// + /// Gets or sets the executable of the application + /// + public string Executable + { + get { return _executable; } + set { _executable = value; } + } + private string _aumid; /// /// Gets or sets the aumid of the application diff --git a/UWPHook/AppManager.cs b/UWPHook/AppManager.cs index cd52f5e..21b4ad2 100644 --- a/UWPHook/AppManager.cs +++ b/UWPHook/AppManager.cs @@ -1,4 +1,5 @@ -using System; +using Serilog; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -7,6 +8,8 @@ using System.Management; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; +using UWPHook.Properties; namespace UWPHook { @@ -17,6 +20,7 @@ namespace UWPHook { private static int runningProcessId; private static bool isLauncherProcess; + private static string executablePath; /// /// Launch a UWP App using a ApplicationActivationManager and sets a internal id to launched proccess id @@ -24,29 +28,29 @@ namespace UWPHook /// The AUMID of the app to launch public static void LaunchUWPApp(string[] args) { - string aumid = args[1]; // We receive the args from Steam, - // 0 is application location, - // 1 is the aumid, the rest are extras - + + + // We receive the args from Steam, + // 0 is application location, + // 1 is the aumid, + // 2 is the executable, the rest are extras + string aumid = args[1]; + executablePath = args[2].Contains("/") ? args[2].Replace('/', '\\') : args[2]; + FileStream debug = File.OpenWrite("debug.log"); + Log.Debug("Arguments => " + String.Join("/", args)); var mgr = new ApplicationActivationManager(); uint processId; - string extra_args = String.Join(" ", args.Skip(2) - .Take(args.Length - 2) + string extra_args = String.Join(" ", args.Skip(3) + .Take(args.Length - 3) .Select(eachElement => eachElement.Clone() ).ToArray()); try { mgr.ActivateApplication(aumid, extra_args, ActivateOptions.None, out processId); - runningProcessId = (int)processId; - - //if this is a launch aided by GameLaunchHelper, deal with child launch later - var possibleLauncher = Process.GetProcessById(runningProcessId); - if (possibleLauncher.ProcessName.Equals("gamelaunchhelper", StringComparison.OrdinalIgnoreCase)) - { - isLauncherProcess = true; - } + runningProcessId = (int) processId; + Log.Debug("Process ID => " + runningProcessId.ToString()); //Bring the launched app to the foreground, this fixes in-home streaming BringProcess(); @@ -63,34 +67,58 @@ namespace UWPHook /// True if the perviously launched app is running, false otherwise public static Boolean IsRunning() { - //If 0, no app was launched most probably - if (runningProcessId != 0) + try { - try + Log.Debug("Checking PID => " + runningProcessId.ToString()); + + // PID 0 means an error during launch for the game + // (Example : Game no more available on gamepass or problem with installation) so instant exit in this case + if (runningProcessId == 0) { - Process.GetProcessById(runningProcessId); - return true; + Log.Debug("PID is 0"); + return false; } - catch + Process.GetProcessById(runningProcessId); + Log.Debug("Process is running"); + return true; + } + catch + { + // Check only at launch if started by a launcher + if (!isLauncherProcess) { - //the process we launched is no longer running. did it spawn any others? - if (isLauncherProcess) + Log.Debug("initial PID is not running anymore, checking other possible process runing before stop"); + bool secondCheck = false; + do { - var tree = GetProcessChildren(); - if (tree.TryGetValue(runningProcessId, out var children)) + // Handle process running by some launcher by checking their executable path and name + var processes = GetProcess(); + foreach (var process in processes) { - //retarget to the first child we find - isLauncherProcess = false; - - var newId = children.First(); - Debug.WriteLine($"Launcher opened child process ({runningProcessId}->{newId}), using new process as target"); - runningProcessId = newId; + string executableFile = executablePath.Contains('\\') ? executablePath.Substring(executablePath.LastIndexOf('\\') + 1) : executablePath; + Log.Debug("Process " + process.Value.Path + " contains " + executablePath + " ? : " + process.Value.Path.Contains(executablePath).ToString()); + Log.Debug("Process " + process.Key + " contains " + executableFile + " ? : " + process.Key.Contains(executableFile).ToString()); + if (process.Value.Path.Contains(executablePath) || process.Key.Contains(executableFile)) + { + int pid = process.Value.Pid; + Log.Debug($"Launcher opened child process ({runningProcessId}->{pid}), using new process as target"); + runningProcessId = pid; + isLauncherProcess = true; + + //bring the "real" launched process + BringProcess(); + return true; + } + } - //bring the "real" launched process - BringProcess(); - return true; + // Handle the last chance if process was not found due to slow running like Farming Simulator 2022 or Halo MCC + secondCheck = !secondCheck; + if (secondCheck) + { + Log.Debug("Process has not been found. Last chance to find it !"); + Thread.Sleep(Settings.Default.Seconds * 5000); } - } + } while (secondCheck); } } @@ -98,26 +126,24 @@ namespace UWPHook } /// - /// Find pid<->parent_pid relationships + /// Find process path with their dedicated pid /// - /// Map of processes to their child IDs. Any process in this object may have already terminated - private static Dictionary> GetProcessChildren() + /// Map of processes to their path and pid. Any process in this object may have already terminated + private static Dictionary GetProcess() { - var result = new Dictionary>(); + var result = new Dictionary(); - using (var searcher = new ManagementObjectSearcher("select processid,parentprocessid from win32_process")) + using (var searcher = new ManagementObjectSearcher("select processid, Name, ExecutablePath from win32_process")) { foreach (var process in searcher.Get()) { + string processName = Convert.ToString(process.Properties["Name"].Value); int processId = Convert.ToInt32(process.Properties["processid"].Value); - int parentId = Convert.ToInt32(process.Properties["parentprocessid"].Value); + string processPath = Convert.ToString(process.Properties["ExecutablePath"].Value); - if (!result.ContainsKey(parentId)) - { - result.Add(parentId, new List()); - } + if (String.IsNullOrWhiteSpace(processName) || result.ContainsKey(processName)) continue; - result[parentId].Add(processId); + result.Add(processName, (processPath, processId)); } } @@ -125,7 +151,7 @@ namespace UWPHook } /// - /// Gets a list of installed UWP Apps on the system, containing each app name + AUMID, separated by '|' + /// Gets a list of installed UWP Apps on the system, containing each app name + AUMID + executable path, separated by '|' /// /// List of installed UWP Apps public static List GetInstalledApps() diff --git a/UWPHook/GamesWindow.xaml.cs b/UWPHook/GamesWindow.xaml.cs index 9043b43..ba1501a 100644 --- a/UWPHook/GamesWindow.xaml.cs +++ b/UWPHook/GamesWindow.xaml.cs @@ -1,4 +1,5 @@ using Force.Crc32; +using Serilog; using SharpSteam; using System; using System.Collections.Generic; @@ -35,6 +36,10 @@ namespace UWPHook Debug.WriteLine("Init GamesWindow"); Apps = new AppEntryModel(); var args = Environment.GetCommandLineArgs(); + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Error() + .WriteTo.File("debug.log", rollingInterval: RollingInterval.Day) + .CreateLogger(); // If null or 1, the app was launched normally if (args?.Length > 1) @@ -400,7 +405,7 @@ namespace UWPHook AppName = app.Name, Exe = exePath, StartDir = exeDir, - LaunchOptions = app.Aumid, + LaunchOptions = app.Aumid + " " + app.Executable, AllowDesktopConfig = 1, AllowOverlay = 1, Icon = icon, @@ -667,7 +672,7 @@ namespace UWPHook string logosPath = Path.GetDirectoryName(values[1]); Application.Current.Dispatcher.BeginInvoke((Action)delegate () { - Apps.Entries.Add(new AppEntry() { Name = values[0], IconPath = logosPath, Aumid = values[2], Selected = false }); + Apps.Entries.Add(new AppEntry() { Name = values[0], Executable = values[3], IconPath = logosPath, Aumid = values[2], Selected = false }); }); } } diff --git a/UWPHook/Resources/GetAUMIDScript.ps1 b/UWPHook/Resources/GetAUMIDScript.ps1 index e03e5ab..7007252 100644 --- a/UWPHook/Resources/GetAUMIDScript.ps1 +++ b/UWPHook/Resources/GetAUMIDScript.ps1 @@ -10,6 +10,11 @@ foreach ($app in $installedapps) { $appx = Get-AppxPackageManifest $app; $name = $appx.Package.Properties.DisplayName; + $executable = (Select-Xml -Path ($app.InstallLocation + "\MicrosoftGame.Config") -XPath "/Game/ExecutableList/Executable/@Name").Node.Value + # Convert object to ensure is the String of execuble (cf Halo Master Chief Collection example below) + # mcclauncher.exe + # MCC\Binaries\Win64\MCCWinStore-Win64-Shipping.exe + if($executable -is [Object[]]) { $executable = $executable[1].ToString() } if($name -like '*DisplayName*' -or $name -like '*ms-resource*') { @@ -22,8 +27,7 @@ foreach ($app in $installedapps) $logo = $app.InstallLocation + "\" + $appx.Package.Applications.Application.VisualElements.Square150x150Logo; - $aumidList += $name + "|" + $logo + "|" + - $app.packagefamilyname + "!" + $id+ ";" + $aumidList += $name + "|" + $logo + "|" + $app.packagefamilyname + "!" + $id + "|" + $executable + ";" } } } diff --git a/UWPHook/UWPHook.csproj b/UWPHook/UWPHook.csproj index de0049c..5885375 100644 --- a/UWPHook/UWPHook.csproj +++ b/UWPHook/UWPHook.csproj @@ -213,6 +213,12 @@ 13.0.1 + + 2.11.0 + + + 5.0.0 +